style: add eslint styling
This commit is contained in:
8
.prettierrc
Normal file
8
.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"semi": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false
|
||||||
|
}
|
37
eslint.config.mjs
Normal file
37
eslint.config.mjs
Normal 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -47,12 +47,18 @@
|
|||||||
"sha.js": "^2.4.11"
|
"sha.js": "^2.4.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.18.0",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/sha.js": "^2.4.4",
|
"@types/sha.js": "^2.4.4",
|
||||||
"@yao-pkg/pkg": "^6.2.0",
|
"@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",
|
"nodemon": "^3.1.9",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.20.0"
|
||||||
},
|
},
|
||||||
"pkg": {
|
"pkg": {
|
||||||
"scripts": "build/**/*",
|
"scripts": "build/**/*",
|
||||||
|
831
pnpm-lock.yaml
generated
831
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,15 @@
|
|||||||
import { BPSet } from "./types";
|
import { BPSet } from './types'
|
||||||
import { readdir } from 'node:fs/promises'
|
import { readdir } from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
export class BPManager {
|
export class BPManager {
|
||||||
private static _instance = new BPManager()
|
private static _instance = new BPManager()
|
||||||
|
|
||||||
public static getInstance = () =>
|
public static getInstance = () => this._instance
|
||||||
this._instance
|
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
private readonly bpSets:
|
private readonly bpSets: Record<string, BPSet> = {}
|
||||||
Record<string, BPSet> = {}
|
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.loadBPSets()
|
this.loadBPSets()
|
||||||
@ -25,7 +23,7 @@ export class BPManager {
|
|||||||
|
|
||||||
for (const bpSetFile of bpSetFiles) {
|
for (const bpSetFile of bpSetFiles) {
|
||||||
const bpSetPath = path.join(__dirname, 'bpsets', bpSetFolder, bpSetFile)
|
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)) {
|
for (const bpSetClass of Object.keys(bpSetClasses)) {
|
||||||
this.bpSets[bpSetClass] = new bpSetClasses[bpSetClass]()
|
this.bpSets[bpSetClass] = new bpSetClasses[bpSetClass]()
|
||||||
@ -39,29 +37,18 @@ export class BPManager {
|
|||||||
return this.bpSets[name].check()
|
return this.bpSets[name].check()
|
||||||
}
|
}
|
||||||
|
|
||||||
public runCheckAll(finished = (name: string) => {}) {
|
public runCheckAll(finished = (_: string) => {}) {
|
||||||
const checkJobs: Promise<void>[] = []
|
const checkJobs: Promise<void>[] = []
|
||||||
|
|
||||||
for (const bpset of Object.values(this.bpSets))
|
for (const bpset of Object.values(this.bpSets))
|
||||||
checkJobs.push(
|
checkJobs.push(bpset.check().then(() => finished(bpset.getMetadata().name)))
|
||||||
bpset
|
|
||||||
.check()
|
|
||||||
.then(() =>
|
|
||||||
finished(bpset.getMetadata().name))
|
|
||||||
)
|
|
||||||
|
|
||||||
return Promise.all(checkJobs)
|
return Promise.all(checkJobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
public runFix(name: string, requiredParametersForFix: { name: string, value: string }[]) {
|
public runFix(name: string, requiredParametersForFix: { name: string; value: string }[]) {
|
||||||
return this
|
return this.bpSets[name].fix(this.bpSets[name].getStats().nonCompliantResources, requiredParametersForFix)
|
||||||
.bpSets[name]
|
|
||||||
.fix(
|
|
||||||
this.bpSets[name].getStats().nonCompliantResources,
|
|
||||||
requiredParametersForFix
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly getBPSets = () =>
|
public readonly getBPSets = () => Object.values(this.bpSets)
|
||||||
Object.values(this.bpSets)
|
|
||||||
}
|
}
|
||||||
|
@ -15,14 +15,13 @@ import shajs from 'sha.js'
|
|||||||
export class Memorizer {
|
export class Memorizer {
|
||||||
private static memorized = new Map<string, 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 serialized = JSON.stringify([client.constructor.name, salt])
|
||||||
const hashed = shajs('sha256').update(serialized).digest('hex')
|
const hashed = shajs('sha256').update(serialized).digest('hex')
|
||||||
|
|
||||||
const memorized = this.memorized.get(hashed)
|
const memorized = this.memorized.get(hashed)
|
||||||
|
|
||||||
if (memorized !== undefined)
|
if (memorized !== undefined) return memorized
|
||||||
return memorized
|
|
||||||
|
|
||||||
const newMemo = new Memorizer(client)
|
const newMemo = new Memorizer(client)
|
||||||
this.memorized.set(hashed, newMemo)
|
this.memorized.set(hashed, newMemo)
|
||||||
@ -31,23 +30,19 @@ export class Memorizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static reset() {
|
public static reset() {
|
||||||
for (const memorized of Memorizer.memorized.values())
|
for (const memorized of Memorizer.memorized.values()) memorized.reset()
|
||||||
memorized.reset()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private memorized = new Map<string, any>()
|
private memorized = new Map<string, unknown>()
|
||||||
|
|
||||||
private constructor (
|
private constructor(private client: Client<unknown, unknown, unknown, unknown>) {}
|
||||||
private client: Client<any, any, any, any>
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public readonly send: typeof this.client.send = async (command) => {
|
public readonly send: typeof this.client.send = async (command) => {
|
||||||
const serialized = JSON.stringify([command.constructor.name, command.input])
|
const serialized = JSON.stringify([command.constructor.name, command.input])
|
||||||
const hashed = shajs('sha256').update(serialized).digest('hex')
|
const hashed = shajs('sha256').update(serialized).digest('hex')
|
||||||
|
|
||||||
const memorized = this.memorized.get(hashed)
|
const memorized = this.memorized.get(hashed)
|
||||||
if (memorized !== undefined)
|
if (memorized !== undefined) return memorized
|
||||||
return memorized
|
|
||||||
|
|
||||||
console.log(command.constructor.name, 'Executed.')
|
console.log(command.constructor.name, 'Executed.')
|
||||||
|
|
||||||
@ -57,6 +52,5 @@ export class Memorizer {
|
|||||||
return newMemo
|
return newMemo
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly reset = () =>
|
private readonly reset = () => this.memorized.clear()
|
||||||
this.memorized.clear()
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
import express, { Request, Response } from 'express'
|
import express, { Request, Response } from 'express'
|
||||||
import { BPManager } from './BPManager'
|
import { BPManager } from './BPManager'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from './types'
|
import { BPSetMetadata, BPSetStats } from './types'
|
||||||
import { Memorizer } from './Memorizer'
|
import { Memorizer } from './Memorizer'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
export class WebServer {
|
export class WebServer {
|
||||||
private readonly app = express()
|
private readonly app = express()
|
||||||
private readonly bpManager =
|
private readonly bpManager = BPManager.getInstance()
|
||||||
BPManager.getInstance()
|
|
||||||
|
|
||||||
constructor (
|
constructor(private readonly port = 2424) {
|
||||||
private readonly port = 2424
|
|
||||||
) {
|
|
||||||
this.app.set('view engine', 'ejs')
|
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('/', this.getMainPage.bind(this))
|
||||||
this.app.get('/check', this.runCheckOnce.bind(this))
|
this.app.get('/check', this.runCheckOnce.bind(this))
|
||||||
@ -30,8 +27,8 @@ export class WebServer {
|
|||||||
private getMainPage(req: Request, res: Response) {
|
private getMainPage(req: Request, res: Response) {
|
||||||
const hidePass = req.query['hidePass'] === 'true'
|
const hidePass = req.query['hidePass'] === 'true'
|
||||||
const bpStatus: {
|
const bpStatus: {
|
||||||
category: string,
|
category: string
|
||||||
metadatas: (BPSetMetadata&BPSetStats)[]
|
metadatas: (BPSetMetadata & BPSetStats)[]
|
||||||
}[] = []
|
}[] = []
|
||||||
|
|
||||||
const bpMetadatas = this.bpManager.getBPSets().map((v, idx) => ({ ...v, idx }))
|
const bpMetadatas = this.bpManager.getBPSets().map((v, idx) => ({ ...v, idx }))
|
||||||
@ -40,11 +37,11 @@ export class WebServer {
|
|||||||
for (const category of categories)
|
for (const category of categories)
|
||||||
bpStatus.push({
|
bpStatus.push({
|
||||||
category,
|
category,
|
||||||
metadatas:
|
metadatas: bpMetadatas
|
||||||
bpMetadatas
|
.filter(
|
||||||
.filter((v) =>
|
(v) =>
|
||||||
v.getMetadata().awsService === category &&
|
v.getMetadata().awsService === category && (!hidePass || v.getStats().nonCompliantResources.length > 0)
|
||||||
(!hidePass || v.getStats().nonCompliantResources.length > 0))
|
)
|
||||||
.map((v) => ({ ...v.getMetadata(), ...v.getStats(), idx: v.idx }))
|
.map((v) => ({ ...v.getMetadata(), ...v.getStats(), idx: v.idx }))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -93,8 +90,7 @@ export class WebServer {
|
|||||||
res.write('<pre>Start Checking....\n')
|
res.write('<pre>Start Checking....\n')
|
||||||
|
|
||||||
Memorizer.reset()
|
Memorizer.reset()
|
||||||
await this.bpManager.runCheckAll((name) =>
|
await this.bpManager.runCheckAll((name) => res.write(`${name} - FINISHED\n`))
|
||||||
res.write(`${name} - FINISHED\n`))
|
|
||||||
|
|
||||||
res.write(`<a href="/?hidePass=${hidePass}">Done. Return to Report Page</a>`)
|
res.write(`<a href="/?hidePass=${hidePass}">Done. Return to Report Page</a>`)
|
||||||
res.write(`<script>window.location.replace('/?hidePass=${hidePass}')</script>`)
|
res.write(`<script>window.location.replace('/?hidePass=${hidePass}')</script>`)
|
||||||
@ -117,27 +113,24 @@ export class WebServer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const requiredParametersForFix =
|
const requiredParametersForFix = Object.keys(req.body).map((k) => ({ name: k, value: req.body[k] }))
|
||||||
Object
|
|
||||||
.keys(req.body)
|
|
||||||
.map((k) => ({ name: k, value: req.body[k] }))
|
|
||||||
|
|
||||||
await this.bpManager.runFix(name, requiredParametersForFix)
|
await this.bpManager.runFix(name, requiredParametersForFix).catch((error) => {
|
||||||
.catch((error) => {
|
res.write(error.toString() + '\n')
|
||||||
res.write(error.toString() + '\n')
|
})
|
||||||
})
|
|
||||||
|
|
||||||
res.write(`<a href="/?hidePass=${hidePass}">Done. Return to Report Page`)
|
res.write(`<a href="/?hidePass=${hidePass}">Done. Return to Report Page`)
|
||||||
res.write(`<script>window.location.replace('/?hidePass=${hidePass}')</script>`)
|
res.write(`<script>window.location.replace('/?hidePass=${hidePass}')</script>`)
|
||||||
res.end()
|
res.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
private error404 (_: Request, res: Response) {
|
private error404(_: Request, res: Response) {
|
||||||
res.status(404).send({ success: false, message: 'Page not found' })
|
res.status(404).send({ success: false, message: 'Page not found' })
|
||||||
}
|
}
|
||||||
|
|
||||||
private showBanner() {
|
private showBanner() {
|
||||||
console.log(`
|
console.log(
|
||||||
|
`
|
||||||
|
|
||||||
_______ _______ _______ _______ _______ _______
|
_______ _______ _______ _______ _______ _______
|
||||||
| _ || || || || || |
|
| _ || || || || || |
|
||||||
@ -151,9 +144,9 @@ export class WebServer {
|
|||||||
Server is now on http://127.0.0.1:${this.port}
|
Server is now on http://127.0.0.1:${this.port}
|
||||||
|
|
||||||
`
|
`
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((v) => v.replace(/ /, ''))
|
.map((v) => v.replace(/ {6}/, ''))
|
||||||
.join('\n')
|
.join('\n')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,26 @@ import {
|
|||||||
ElasticLoadBalancingV2Client,
|
ElasticLoadBalancingV2Client,
|
||||||
DescribeLoadBalancersCommand,
|
DescribeLoadBalancersCommand,
|
||||||
DescribeLoadBalancerAttributesCommand,
|
DescribeLoadBalancerAttributesCommand,
|
||||||
ModifyLoadBalancerAttributesCommand,
|
ModifyLoadBalancerAttributesCommand
|
||||||
} from '@aws-sdk/client-elastic-load-balancing-v2';
|
} from '@aws-sdk/client-elastic-load-balancing-v2'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ALBHttpDropInvalidHeaderEnabled implements BPSet {
|
export class ALBHttpDropInvalidHeaderEnabled implements BPSet {
|
||||||
private readonly client = new ElasticLoadBalancingV2Client({});
|
private readonly client = new ElasticLoadBalancingV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getLoadBalancers = async () => {
|
private readonly getLoadBalancers = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
|
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
|
||||||
return response.LoadBalancers || [];
|
return response.LoadBalancers || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(
|
||||||
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
||||||
);
|
)
|
||||||
return response.Attributes || [];
|
return response.Attributes || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ALBHttpDropInvalidHeaderEnabled',
|
name: 'ALBHttpDropInvalidHeaderEnabled',
|
||||||
@ -36,95 +36,92 @@ export class ALBHttpDropInvalidHeaderEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeLoadBalancerAttributesCommand',
|
name: 'DescribeLoadBalancerAttributesCommand',
|
||||||
reason: 'Verify if invalid headers are dropped for ALBs.',
|
reason: 'Verify if invalid headers are dropped for ALBs.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyLoadBalancerAttributesCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const loadBalancers = await this.getLoadBalancers();
|
const loadBalancers = await this.getLoadBalancers()
|
||||||
|
|
||||||
for (const lb of loadBalancers) {
|
for (const lb of loadBalancers) {
|
||||||
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
|
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
|
||||||
const isEnabled = attributes.some(
|
const isEnabled = attributes.some(
|
||||||
(attr) =>
|
(attr) => attr.Key === 'routing.http.drop_invalid_header_fields.enabled' && attr.Value === 'true'
|
||||||
attr.Key === 'routing.http.drop_invalid_header_fields.enabled' && attr.Value === 'true'
|
)
|
||||||
);
|
|
||||||
|
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
compliantResources.push(lb.LoadBalancerArn!);
|
compliantResources.push(lb.LoadBalancerArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(lb.LoadBalancerArn!);
|
nonCompliantResources.push(lb.LoadBalancerArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const lbArn of nonCompliantResources) {
|
for (const lbArn of nonCompliantResources) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyLoadBalancerAttributesCommand({
|
new ModifyLoadBalancerAttributesCommand({
|
||||||
LoadBalancerArn: lbArn,
|
LoadBalancerArn: lbArn,
|
||||||
Attributes: [
|
Attributes: [{ Key: 'routing.http.drop_invalid_header_fields.enabled', Value: 'true' }]
|
||||||
{ Key: 'routing.http.drop_invalid_header_fields.enabled', Value: 'true' },
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,37 +13,36 @@ export class ALBWAFEnabled implements BPSet {
|
|||||||
return response.LoadBalancers || []
|
return response.LoadBalancers || []
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => (
|
public readonly getMetadata = () => ({
|
||||||
{
|
name: 'ALBWAFEnabled',
|
||||||
name: 'ALBWAFEnabled',
|
description: 'Ensures that WAF is associated with ALBs.',
|
||||||
description: 'Ensures that WAF is associated with ALBs.',
|
priority: 1,
|
||||||
priority: 1,
|
priorityReason: 'Associating WAF with ALBs protects against common web attacks.',
|
||||||
priorityReason: 'Associating WAF with ALBs protects against common web attacks.',
|
awsService: 'Elastic Load Balancing',
|
||||||
awsService: 'Elastic Load Balancing',
|
awsServiceCategory: 'Application Load Balancer',
|
||||||
awsServiceCategory: 'Application Load Balancer',
|
bestPracticeCategory: 'Security',
|
||||||
bestPracticeCategory: 'Security',
|
requiredParametersForFix: [
|
||||||
requiredParametersForFix: [
|
{
|
||||||
{
|
name: 'web-acl-arn',
|
||||||
name: 'web-acl-arn',
|
description: 'The ARN of the WAF ACL to associate with the ALB.',
|
||||||
description: 'The ARN of the WAF ACL to associate with the ALB.',
|
default: '',
|
||||||
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,
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
commandUsedInCheckFunction: [
|
||||||
commandUsedInCheckFunction: [
|
{
|
||||||
{
|
name: 'GetWebAclForResourceCommand',
|
||||||
name: 'GetWebAclForResourceCommand',
|
reason: 'Check if a WAF is associated with the ALB.'
|
||||||
reason: 'Check if a WAF is associated with the ALB.'
|
}
|
||||||
}
|
],
|
||||||
],
|
commandUsedInFixFunction: [
|
||||||
commandUsedInFixFunction: [
|
{
|
||||||
{
|
name: 'AssociateWebAclCommand',
|
||||||
name: 'AssociateWebAclCommand',
|
reason: 'Associate a WAF ACL with the ALB.'
|
||||||
reason: 'Associate a WAF ACL with the ALB.'
|
}
|
||||||
}
|
],
|
||||||
],
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
@ -53,8 +52,7 @@ export class ALBWAFEnabled implements BPSet {
|
|||||||
errorMessage: []
|
errorMessage: []
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly getStats = () =>
|
public readonly getStats = () => this.stats
|
||||||
this.stats
|
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = []
|
this.stats.compliantResources = []
|
||||||
@ -66,17 +64,16 @@ export class ALBWAFEnabled implements BPSet {
|
|||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl().then(
|
||||||
.then(
|
() => (this.stats.status = 'FINISHED'),
|
||||||
() => this.stats.status = 'FINISHED',
|
(err) => {
|
||||||
(err) => {
|
this.stats.status = 'ERROR'
|
||||||
this.stats.status = 'ERROR'
|
this.stats.errorMessage.push({
|
||||||
this.stats.errorMessage.push({
|
date: new Date(),
|
||||||
date: new Date(),
|
message: err
|
||||||
message: err
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
@ -85,9 +82,7 @@ export class ALBWAFEnabled implements BPSet {
|
|||||||
const loadBalancers = await this.getLoadBalancers()
|
const loadBalancers = await this.getLoadBalancers()
|
||||||
|
|
||||||
for (const lb of loadBalancers) {
|
for (const lb of loadBalancers) {
|
||||||
const response = await this.wafClient.send(
|
const response = await this.wafClient.send(new GetWebACLForResourceCommand({ ResourceArn: lb.LoadBalancerArn }))
|
||||||
new GetWebACLForResourceCommand({ ResourceArn: lb.LoadBalancerArn })
|
|
||||||
)
|
|
||||||
if (response.WebACL) {
|
if (response.WebACL) {
|
||||||
compliantResources.push(lb.LoadBalancerArn!)
|
compliantResources.push(lb.LoadBalancerArn!)
|
||||||
} else {
|
} else {
|
||||||
@ -100,23 +95,21 @@ export class ALBWAFEnabled implements BPSet {
|
|||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args)
|
await this.fixImpl(...args).then(
|
||||||
.then(
|
() => (this.stats.status = 'FINISHED'),
|
||||||
() => this.stats.status = 'FINISHED',
|
(err) => {
|
||||||
(err) => {
|
this.stats.status = 'ERROR'
|
||||||
this.stats.status = 'ERROR'
|
this.stats.errorMessage.push({
|
||||||
this.stats.errorMessage.push({
|
date: new Date(),
|
||||||
date: new Date(),
|
message: err
|
||||||
message: err
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
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) {
|
if (!webAclArn) {
|
||||||
throw new Error("Required parameter 'web-acl-arn' is missing.")
|
throw new Error("Required parameter 'web-acl-arn' is missing.")
|
||||||
|
@ -2,26 +2,26 @@ import {
|
|||||||
ElasticLoadBalancingV2Client,
|
ElasticLoadBalancingV2Client,
|
||||||
DescribeLoadBalancersCommand,
|
DescribeLoadBalancersCommand,
|
||||||
DescribeLoadBalancerAttributesCommand,
|
DescribeLoadBalancerAttributesCommand,
|
||||||
ModifyLoadBalancerAttributesCommand,
|
ModifyLoadBalancerAttributesCommand
|
||||||
} from '@aws-sdk/client-elastic-load-balancing-v2';
|
} from '@aws-sdk/client-elastic-load-balancing-v2'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ELBCrossZoneLoadBalancingEnabled implements BPSet {
|
export class ELBCrossZoneLoadBalancingEnabled implements BPSet {
|
||||||
private readonly client = new ElasticLoadBalancingV2Client({});
|
private readonly client = new ElasticLoadBalancingV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getLoadBalancers = async () => {
|
private readonly getLoadBalancers = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
|
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
|
||||||
return response.LoadBalancers || [];
|
return response.LoadBalancers || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(
|
||||||
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
||||||
);
|
)
|
||||||
return response.Attributes || [];
|
return response.Attributes || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ELBCrossZoneLoadBalancingEnabled',
|
name: 'ELBCrossZoneLoadBalancingEnabled',
|
||||||
@ -36,93 +36,92 @@ export class ELBCrossZoneLoadBalancingEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeLoadBalancerAttributesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyLoadBalancerAttributesCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const loadBalancers = await this.getLoadBalancers();
|
const loadBalancers = await this.getLoadBalancers()
|
||||||
|
|
||||||
for (const lb of loadBalancers) {
|
for (const lb of loadBalancers) {
|
||||||
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
|
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
|
||||||
const isEnabled = attributes.some(
|
const isEnabled = attributes.some(
|
||||||
(attr) =>
|
(attr) => attr.Key === 'load_balancing.cross_zone.enabled' && attr.Value === 'true'
|
||||||
attr.Key === 'load_balancing.cross_zone.enabled' && attr.Value === 'true'
|
)
|
||||||
);
|
|
||||||
|
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
compliantResources.push(lb.LoadBalancerArn!);
|
compliantResources.push(lb.LoadBalancerArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(lb.LoadBalancerArn!);
|
nonCompliantResources.push(lb.LoadBalancerArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const lbArn of nonCompliantResources) {
|
for (const lbArn of nonCompliantResources) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyLoadBalancerAttributesCommand({
|
new ModifyLoadBalancerAttributesCommand({
|
||||||
LoadBalancerArn: lbArn,
|
LoadBalancerArn: lbArn,
|
||||||
Attributes: [{ Key: 'load_balancing.cross_zone.enabled', Value: 'true' }],
|
Attributes: [{ Key: 'load_balancing.cross_zone.enabled', Value: 'true' }]
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,26 @@ import {
|
|||||||
ElasticLoadBalancingV2Client,
|
ElasticLoadBalancingV2Client,
|
||||||
DescribeLoadBalancersCommand,
|
DescribeLoadBalancersCommand,
|
||||||
DescribeLoadBalancerAttributesCommand,
|
DescribeLoadBalancerAttributesCommand,
|
||||||
ModifyLoadBalancerAttributesCommand,
|
ModifyLoadBalancerAttributesCommand
|
||||||
} from '@aws-sdk/client-elastic-load-balancing-v2';
|
} from '@aws-sdk/client-elastic-load-balancing-v2'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ELBDeletionProtectionEnabled implements BPSet {
|
export class ELBDeletionProtectionEnabled implements BPSet {
|
||||||
private readonly client = new ElasticLoadBalancingV2Client({});
|
private readonly client = new ElasticLoadBalancingV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getLoadBalancers = async () => {
|
private readonly getLoadBalancers = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
|
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
|
||||||
return response.LoadBalancers || [];
|
return response.LoadBalancers || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(
|
||||||
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
||||||
);
|
)
|
||||||
return response.Attributes || [];
|
return response.Attributes || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ELBDeletionProtectionEnabled',
|
name: 'ELBDeletionProtectionEnabled',
|
||||||
@ -36,93 +36,90 @@ export class ELBDeletionProtectionEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeLoadBalancerAttributesCommand',
|
name: 'DescribeLoadBalancerAttributesCommand',
|
||||||
reason: 'Verify if deletion protection is enabled for ELBs.',
|
reason: 'Verify if deletion protection is enabled for ELBs.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyLoadBalancerAttributesCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const loadBalancers = await this.getLoadBalancers();
|
const loadBalancers = await this.getLoadBalancers()
|
||||||
|
|
||||||
for (const lb of loadBalancers) {
|
for (const lb of loadBalancers) {
|
||||||
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
|
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
|
||||||
const isEnabled = attributes.some(
|
const isEnabled = attributes.some((attr) => attr.Key === 'deletion_protection.enabled' && attr.Value === 'true')
|
||||||
(attr) =>
|
|
||||||
attr.Key === 'deletion_protection.enabled' && attr.Value === 'true'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
compliantResources.push(lb.LoadBalancerArn!);
|
compliantResources.push(lb.LoadBalancerArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(lb.LoadBalancerArn!);
|
nonCompliantResources.push(lb.LoadBalancerArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const lbArn of nonCompliantResources) {
|
for (const lbArn of nonCompliantResources) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyLoadBalancerAttributesCommand({
|
new ModifyLoadBalancerAttributesCommand({
|
||||||
LoadBalancerArn: lbArn,
|
LoadBalancerArn: lbArn,
|
||||||
Attributes: [{ Key: 'deletion_protection.enabled', Value: 'true' }],
|
Attributes: [{ Key: 'deletion_protection.enabled', Value: 'true' }]
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,26 @@ import {
|
|||||||
ElasticLoadBalancingV2Client,
|
ElasticLoadBalancingV2Client,
|
||||||
DescribeLoadBalancersCommand,
|
DescribeLoadBalancersCommand,
|
||||||
DescribeLoadBalancerAttributesCommand,
|
DescribeLoadBalancerAttributesCommand,
|
||||||
ModifyLoadBalancerAttributesCommand,
|
ModifyLoadBalancerAttributesCommand
|
||||||
} from '@aws-sdk/client-elastic-load-balancing-v2';
|
} from '@aws-sdk/client-elastic-load-balancing-v2'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ELBLoggingEnabled implements BPSet {
|
export class ELBLoggingEnabled implements BPSet {
|
||||||
private readonly client = new ElasticLoadBalancingV2Client({});
|
private readonly client = new ElasticLoadBalancingV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getLoadBalancers = async () => {
|
private readonly getLoadBalancers = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}));
|
const response = await this.memoClient.send(new DescribeLoadBalancersCommand({}))
|
||||||
return response.LoadBalancers || [];
|
return response.LoadBalancers || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
private readonly getLoadBalancerAttributes = async (loadBalancerArn: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(
|
||||||
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
new DescribeLoadBalancerAttributesCommand({ LoadBalancerArn: loadBalancerArn })
|
||||||
);
|
)
|
||||||
return response.Attributes || [];
|
return response.Attributes || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ELBLoggingEnabled',
|
name: 'ELBLoggingEnabled',
|
||||||
@ -32,97 +32,100 @@ export class ELBLoggingEnabled implements BPSet {
|
|||||||
awsServiceCategory: 'Classic Load Balancer',
|
awsServiceCategory: 'Classic Load Balancer',
|
||||||
bestPracticeCategory: 'Logging and Monitoring',
|
bestPracticeCategory: 'Logging and Monitoring',
|
||||||
requiredParametersForFix: [
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeLoadBalancerAttributesCommand',
|
name: 'DescribeLoadBalancerAttributesCommand',
|
||||||
reason: 'Verify if access logging is enabled for ELBs.',
|
reason: 'Verify if access logging is enabled for ELBs.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyLoadBalancerAttributesCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const loadBalancers = await this.getLoadBalancers();
|
const loadBalancers = await this.getLoadBalancers()
|
||||||
|
|
||||||
for (const lb of loadBalancers) {
|
for (const lb of loadBalancers) {
|
||||||
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!);
|
const attributes = await this.getLoadBalancerAttributes(lb.LoadBalancerArn!)
|
||||||
const isEnabled = attributes.some(
|
const isEnabled = attributes.some((attr) => attr.Key === 'access_logs.s3.enabled' && attr.Value === 'true')
|
||||||
(attr) => attr.Key === 'access_logs.s3.enabled' && attr.Value === 'true'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
compliantResources.push(lb.LoadBalancerArn!);
|
compliantResources.push(lb.LoadBalancerArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(lb.LoadBalancerArn!);
|
nonCompliantResources.push(lb.LoadBalancerArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const bucketName = requiredParametersForFix.find((param) => param.name === 's3-bucket-name')?.value;
|
const bucketName = requiredParametersForFix.find((param) => param.name === 's3-bucket-name')?.value
|
||||||
const bucketPrefix = requiredParametersForFix.find((param) => param.name === 's3-prefix')?.value;
|
const bucketPrefix = requiredParametersForFix.find((param) => param.name === 's3-prefix')?.value
|
||||||
|
|
||||||
if (!bucketName || !bucketPrefix) {
|
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) {
|
for (const lbArn of nonCompliantResources) {
|
||||||
@ -132,10 +135,10 @@ export class ELBLoggingEnabled implements BPSet {
|
|||||||
Attributes: [
|
Attributes: [
|
||||||
{ Key: 'access_logs.s3.enabled', Value: 'true' },
|
{ Key: 'access_logs.s3.enabled', Value: 'true' },
|
||||||
{ Key: 'access_logs.s3.bucket', Value: bucketName },
|
{ Key: 'access_logs.s3.bucket', Value: bucketName },
|
||||||
{ Key: 'access_logs.s3.prefix', Value: bucketPrefix },
|
{ Key: 'access_logs.s3.prefix', Value: bucketPrefix }
|
||||||
],
|
]
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,22 @@
|
|||||||
import {
|
import { ApiGatewayV2Client, GetApisCommand, GetStagesCommand } from '@aws-sdk/client-apigatewayv2'
|
||||||
ApiGatewayV2Client,
|
import { WAFV2Client, GetWebACLForResourceCommand, AssociateWebACLCommand } from '@aws-sdk/client-wafv2'
|
||||||
GetApisCommand,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
GetStagesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
} 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 {
|
export class APIGatewayAssociatedWithWAF implements BPSet {
|
||||||
private readonly client = new ApiGatewayV2Client({});
|
private readonly client = new ApiGatewayV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
private readonly wafClient = Memorizer.memo(new WAFV2Client({}));
|
private readonly wafClient = Memorizer.memo(new WAFV2Client({}))
|
||||||
|
|
||||||
private readonly getHttpApis = async () => {
|
private readonly getHttpApis = async () => {
|
||||||
const response = await this.memoClient.send(new GetApisCommand({}));
|
const response = await this.memoClient.send(new GetApisCommand({}))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getStages = async (apiId: string) => {
|
private readonly getStages = async (apiId: string) => {
|
||||||
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }));
|
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'APIGatewayAssociatedWithWAF',
|
name: 'APIGatewayAssociatedWithWAF',
|
||||||
@ -39,108 +31,106 @@ export class APIGatewayAssociatedWithWAF implements BPSet {
|
|||||||
name: 'web-acl-arn',
|
name: 'web-acl-arn',
|
||||||
description: 'The ARN of the WAF ACL to associate with the API Gateway stage.',
|
description: 'The ARN of the WAF ACL to associate with the API Gateway stage.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetWebACLForResourceCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'AssociateWebACLCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const apis = await this.getHttpApis();
|
const apis = await this.getHttpApis()
|
||||||
|
|
||||||
for (const api of apis) {
|
for (const api of apis) {
|
||||||
const stages = await this.getStages(api.ApiId!);
|
const stages = await this.getStages(api.ApiId!)
|
||||||
for (const stage of stages) {
|
for (const stage of stages) {
|
||||||
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`;
|
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`
|
||||||
const response = await this.wafClient.send(
|
const response = await this.wafClient.send(new GetWebACLForResourceCommand({ ResourceArn: stageArn }))
|
||||||
new GetWebACLForResourceCommand({ ResourceArn: stageArn })
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.WebACL) {
|
if (response.WebACL) {
|
||||||
compliantResources.push(stageArn);
|
compliantResources.push(stageArn)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(stageArn);
|
nonCompliantResources.push(stageArn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
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) {
|
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) {
|
for (const stageArn of nonCompliantResources) {
|
||||||
await this.wafClient.send(
|
await this.wafClient.send(
|
||||||
new AssociateWebACLCommand({
|
new AssociateWebACLCommand({
|
||||||
ResourceArn: stageArn,
|
ResourceArn: stageArn,
|
||||||
WebACLArn: webAclArn,
|
WebACLArn: webAclArn
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
import {
|
import { ApiGatewayV2Client, GetApisCommand, GetStagesCommand, UpdateStageCommand } from '@aws-sdk/client-apigatewayv2'
|
||||||
ApiGatewayV2Client,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
GetApisCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetStagesCommand,
|
|
||||||
UpdateStageCommand,
|
|
||||||
} from '@aws-sdk/client-apigatewayv2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class APIGatewayExecutionLoggingEnabled implements BPSet {
|
export class APIGatewayExecutionLoggingEnabled implements BPSet {
|
||||||
private readonly client = new ApiGatewayV2Client({});
|
private readonly client = new ApiGatewayV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getHttpApis = async () => {
|
private readonly getHttpApis = async () => {
|
||||||
const response = await this.memoClient.send(new GetApisCommand({}));
|
const response = await this.memoClient.send(new GetApisCommand({}))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getStages = async (apiId: string) => {
|
private readonly getStages = async (apiId: string) => {
|
||||||
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }));
|
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'APIGatewayExecutionLoggingEnabled',
|
name: 'APIGatewayExecutionLoggingEnabled',
|
||||||
@ -34,103 +29,101 @@ export class APIGatewayExecutionLoggingEnabled implements BPSet {
|
|||||||
name: 'log-destination-arn',
|
name: 'log-destination-arn',
|
||||||
description: 'The ARN of the CloudWatch log group for storing API Gateway logs.',
|
description: 'The ARN of the CloudWatch log group for storing API Gateway logs.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetStagesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateStageCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const apis = await this.getHttpApis();
|
const apis = await this.getHttpApis()
|
||||||
|
|
||||||
for (const api of apis) {
|
for (const api of apis) {
|
||||||
const stages = await this.getStages(api.ApiId!);
|
const stages = await this.getStages(api.ApiId!)
|
||||||
for (const stage of stages) {
|
for (const stage of stages) {
|
||||||
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`;
|
const stageArn = `arn:aws:apigateway:${this.client.config.region}::/apis/${api.ApiId}/stages/${stage.StageName}`
|
||||||
const loggingLevel = stage.AccessLogSettings?.Format;
|
const loggingLevel = stage.AccessLogSettings?.Format
|
||||||
|
|
||||||
if (loggingLevel && loggingLevel !== 'OFF') {
|
if (loggingLevel && loggingLevel !== 'OFF') {
|
||||||
compliantResources.push(stageArn);
|
compliantResources.push(stageArn)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(stageArn);
|
nonCompliantResources.push(stageArn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const logDestinationArn = requiredParametersForFix.find(
|
const logDestinationArn = requiredParametersForFix.find((param) => param.name === 'log-destination-arn')?.value
|
||||||
(param) => param.name === 'log-destination-arn'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!logDestinationArn) {
|
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) {
|
for (const stageArn of nonCompliantResources) {
|
||||||
const [apiId, stageName] = stageArn.split('/').slice(-2);
|
const [apiId, stageName] = stageArn.split('/').slice(-2)
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateStageCommand({
|
new UpdateStageCommand({
|
||||||
@ -138,10 +131,10 @@ export class APIGatewayExecutionLoggingEnabled implements BPSet {
|
|||||||
StageName: stageName,
|
StageName: stageName,
|
||||||
AccessLogSettings: {
|
AccessLogSettings: {
|
||||||
DestinationArn: logDestinationArn,
|
DestinationArn: logDestinationArn,
|
||||||
Format: '$context.requestId',
|
Format: '$context.requestId'
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
import {
|
import { ApiGatewayV2Client, GetApisCommand, GetStagesCommand, UpdateStageCommand } from '@aws-sdk/client-apigatewayv2'
|
||||||
ApiGatewayV2Client,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
GetApisCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetStagesCommand,
|
|
||||||
UpdateStageCommand,
|
|
||||||
} from '@aws-sdk/client-apigatewayv2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class APIGatewayV2AccessLogsEnabled implements BPSet {
|
export class APIGatewayV2AccessLogsEnabled implements BPSet {
|
||||||
private readonly client = new ApiGatewayV2Client({});
|
private readonly client = new ApiGatewayV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getHttpApis = async () => {
|
private readonly getHttpApis = async () => {
|
||||||
const response = await this.memoClient.send(new GetApisCommand({}));
|
const response = await this.memoClient.send(new GetApisCommand({}))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getStages = async (apiId: string) => {
|
private readonly getStages = async (apiId: string) => {
|
||||||
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }));
|
const response = await this.memoClient.send(new GetStagesCommand({ ApiId: apiId }))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'APIGatewayV2AccessLogsEnabled',
|
name: 'APIGatewayV2AccessLogsEnabled',
|
||||||
@ -34,104 +29,102 @@ export class APIGatewayV2AccessLogsEnabled implements BPSet {
|
|||||||
name: 'log-destination-arn',
|
name: 'log-destination-arn',
|
||||||
description: 'The ARN of the CloudWatch log group for storing API Gateway logs.',
|
description: 'The ARN of the CloudWatch log group for storing API Gateway logs.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetStagesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateStageCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const apis = await this.getHttpApis();
|
const apis = await this.getHttpApis()
|
||||||
|
|
||||||
for (const api of apis) {
|
for (const api of apis) {
|
||||||
const stages = await this.getStages(api.ApiId!);
|
const stages = await this.getStages(api.ApiId!)
|
||||||
for (const stage of stages) {
|
for (const stage of stages) {
|
||||||
const stageIdentifier = `${api.Name!} / ${stage.StageName!}`;
|
const stageIdentifier = `${api.Name!} / ${stage.StageName!}`
|
||||||
if (!stage.AccessLogSettings) {
|
if (!stage.AccessLogSettings) {
|
||||||
nonCompliantResources.push(stageIdentifier);
|
nonCompliantResources.push(stageIdentifier)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(stageIdentifier);
|
compliantResources.push(stageIdentifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const logDestinationArn = requiredParametersForFix.find(
|
const logDestinationArn = requiredParametersForFix.find((param) => param.name === 'log-destination-arn')?.value
|
||||||
(param) => param.name === 'log-destination-arn'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!logDestinationArn) {
|
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) {
|
for (const resource of nonCompliantResources) {
|
||||||
const [apiName, stageName] = resource.split(' / ');
|
const [apiName, stageName] = resource.split(' / ')
|
||||||
const api = (await this.getHttpApis()).find((a) => a.Name === apiName);
|
const api = (await this.getHttpApis()).find((a) => a.Name === apiName)
|
||||||
|
|
||||||
if (!api) continue;
|
if (!api) continue
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateStageCommand({
|
new UpdateStageCommand({
|
||||||
@ -139,10 +132,10 @@ export class APIGatewayV2AccessLogsEnabled implements BPSet {
|
|||||||
StageName: stageName,
|
StageName: stageName,
|
||||||
AccessLogSettings: {
|
AccessLogSettings: {
|
||||||
DestinationArn: logDestinationArn,
|
DestinationArn: logDestinationArn,
|
||||||
Format: '$context.requestId',
|
Format: '$context.requestId'
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
import {
|
import { ApiGatewayV2Client, GetApisCommand, GetRoutesCommand, UpdateRouteCommand } from '@aws-sdk/client-apigatewayv2'
|
||||||
ApiGatewayV2Client,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
GetApisCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetRoutesCommand,
|
|
||||||
UpdateRouteCommand,
|
|
||||||
} from '@aws-sdk/client-apigatewayv2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class APIGatewayV2AuthorizationTypeConfigured implements BPSet {
|
export class APIGatewayV2AuthorizationTypeConfigured implements BPSet {
|
||||||
private readonly client = new ApiGatewayV2Client({});
|
private readonly client = new ApiGatewayV2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getHttpApis = async () => {
|
private readonly getHttpApis = async () => {
|
||||||
const response = await this.memoClient.send(new GetApisCommand({}));
|
const response = await this.memoClient.send(new GetApisCommand({}))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getRoutes = async (apiId: string) => {
|
private readonly getRoutes = async (apiId: string) => {
|
||||||
const response = await this.memoClient.send(new GetRoutesCommand({ ApiId: apiId }));
|
const response = await this.memoClient.send(new GetRoutesCommand({ ApiId: apiId }))
|
||||||
return response.Items || [];
|
return response.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'APIGatewayV2AuthorizationTypeConfigured',
|
name: 'APIGatewayV2AuthorizationTypeConfigured',
|
||||||
@ -34,117 +29,115 @@ export class APIGatewayV2AuthorizationTypeConfigured implements BPSet {
|
|||||||
name: 'authorization-type',
|
name: 'authorization-type',
|
||||||
description: 'The authorization type to configure for the routes.',
|
description: 'The authorization type to configure for the routes.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'JWT',
|
example: 'JWT'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetRoutesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateRouteCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const apis = await this.getHttpApis();
|
const apis = await this.getHttpApis()
|
||||||
|
|
||||||
for (const api of apis) {
|
for (const api of apis) {
|
||||||
const routes = await this.getRoutes(api.ApiId!);
|
const routes = await this.getRoutes(api.ApiId!)
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
const routeIdentifier = `${api.Name!} / ${route.RouteKey!}`;
|
const routeIdentifier = `${api.Name!} / ${route.RouteKey!}`
|
||||||
if (route.AuthorizationType === 'NONE') {
|
if (route.AuthorizationType === 'NONE') {
|
||||||
nonCompliantResources.push(routeIdentifier);
|
nonCompliantResources.push(routeIdentifier)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(routeIdentifier);
|
compliantResources.push(routeIdentifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const authorizationType = requiredParametersForFix.find(
|
const authorizationType = requiredParametersForFix.find((param) => param.name === 'authorization-type')?.value
|
||||||
(param) => param.name === 'authorization-type'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!authorizationType) {
|
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) {
|
for (const resource of nonCompliantResources) {
|
||||||
const [apiName, routeKey] = resource.split(' / ');
|
const [apiName, routeKey] = resource.split(' / ')
|
||||||
const api = (await this.getHttpApis()).find((a) => a.Name === apiName);
|
const api = (await this.getHttpApis()).find((a) => a.Name === apiName)
|
||||||
|
|
||||||
if (!api) continue;
|
if (!api) continue
|
||||||
|
|
||||||
const routes = await this.getRoutes(api.ApiId!);
|
const routes = await this.getRoutes(api.ApiId!)
|
||||||
const route = routes.find((r) => r.RouteKey === routeKey);
|
const route = routes.find((r) => r.RouteKey === routeKey)
|
||||||
|
|
||||||
if (!route) continue;
|
if (!route) continue
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateRouteCommand({
|
new UpdateRouteCommand({
|
||||||
ApiId: api.ApiId!,
|
ApiId: api.ApiId!,
|
||||||
RouteId: route.RouteId!,
|
RouteId: route.RouteId!,
|
||||||
AuthorizationType: authorizationType as any,
|
AuthorizationType: authorizationType as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
AutoScalingClient,
|
AutoScalingClient,
|
||||||
DescribeAutoScalingGroupsCommand,
|
DescribeAutoScalingGroupsCommand,
|
||||||
UpdateAutoScalingGroupCommand,
|
UpdateAutoScalingGroupCommand
|
||||||
} from '@aws-sdk/client-auto-scaling';
|
} from '@aws-sdk/client-auto-scaling'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class AutoScalingGroupELBHealthCheckRequired implements BPSet {
|
export class AutoScalingGroupELBHealthCheckRequired implements BPSet {
|
||||||
private readonly client = new AutoScalingClient({});
|
private readonly client = new AutoScalingClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getAutoScalingGroups = async () => {
|
private readonly getAutoScalingGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}))
|
||||||
return response.AutoScalingGroups || [];
|
return response.AutoScalingGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'AutoScalingGroupELBHealthCheckRequired',
|
name: 'AutoScalingGroupELBHealthCheckRequired',
|
||||||
@ -28,91 +28,88 @@ export class AutoScalingGroupELBHealthCheckRequired implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeAutoScalingGroupsCommand',
|
name: 'DescribeAutoScalingGroupsCommand',
|
||||||
reason: 'Retrieve Auto Scaling groups to check health check type.',
|
reason: 'Retrieve Auto Scaling groups to check health check type.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateAutoScalingGroupCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const asgs = await this.getAutoScalingGroups();
|
const asgs = await this.getAutoScalingGroups()
|
||||||
|
|
||||||
for (const asg of asgs) {
|
for (const asg of asgs) {
|
||||||
if (
|
if ((asg.LoadBalancerNames?.length || asg.TargetGroupARNs?.length) && asg.HealthCheckType !== 'ELB') {
|
||||||
(asg.LoadBalancerNames?.length || asg.TargetGroupARNs?.length) &&
|
nonCompliantResources.push(asg.AutoScalingGroupARN!)
|
||||||
asg.HealthCheckType !== 'ELB'
|
|
||||||
) {
|
|
||||||
nonCompliantResources.push(asg.AutoScalingGroupARN!);
|
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(asg.AutoScalingGroupARN!);
|
compliantResources.push(asg.AutoScalingGroupARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const asgArn of nonCompliantResources) {
|
for (const asgArn of nonCompliantResources) {
|
||||||
const asgName = asgArn.split(':').pop()!;
|
const asgName = asgArn.split(':').pop()!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateAutoScalingGroupCommand({
|
new UpdateAutoScalingGroupCommand({
|
||||||
AutoScalingGroupName: asgName,
|
AutoScalingGroupName: asgName,
|
||||||
HealthCheckType: 'ELB',
|
HealthCheckType: 'ELB'
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
AutoScalingClient,
|
AutoScalingClient,
|
||||||
DescribeAutoScalingGroupsCommand,
|
DescribeAutoScalingGroupsCommand,
|
||||||
UpdateAutoScalingGroupCommand,
|
UpdateAutoScalingGroupCommand
|
||||||
} from '@aws-sdk/client-auto-scaling';
|
} from '@aws-sdk/client-auto-scaling'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class AutoScalingLaunchTemplate implements BPSet {
|
export class AutoScalingLaunchTemplate implements BPSet {
|
||||||
private readonly client = new AutoScalingClient({});
|
private readonly client = new AutoScalingClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getAutoScalingGroups = async () => {
|
private readonly getAutoScalingGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}))
|
||||||
return response.AutoScalingGroups || [];
|
return response.AutoScalingGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'AutoScalingLaunchTemplate',
|
name: 'AutoScalingLaunchTemplate',
|
||||||
@ -24,107 +24,116 @@ export class AutoScalingLaunchTemplate implements BPSet {
|
|||||||
awsServiceCategory: 'Compute',
|
awsServiceCategory: 'Compute',
|
||||||
bestPracticeCategory: 'Management',
|
bestPracticeCategory: 'Management',
|
||||||
requiredParametersForFix: [
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeAutoScalingGroupsCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateAutoScalingGroupCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const asgs = await this.getAutoScalingGroups();
|
const asgs = await this.getAutoScalingGroups()
|
||||||
|
|
||||||
for (const asg of asgs) {
|
for (const asg of asgs) {
|
||||||
if (asg.LaunchConfigurationName) {
|
if (asg.LaunchConfigurationName) {
|
||||||
nonCompliantResources.push(asg.AutoScalingGroupARN!);
|
nonCompliantResources.push(asg.AutoScalingGroupARN!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(asg.AutoScalingGroupARN!);
|
compliantResources.push(asg.AutoScalingGroupARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const launchTemplateId = requiredParametersForFix.find(
|
const launchTemplateId = requiredParametersForFix.find((param) => param.name === 'launch-template-id')?.value
|
||||||
(param) => param.name === 'launch-template-id'
|
const version = requiredParametersForFix.find((param) => param.name === 'version')?.value
|
||||||
)?.value;
|
|
||||||
const version = requiredParametersForFix.find((param) => param.name === 'version')?.value;
|
|
||||||
|
|
||||||
if (!launchTemplateId || !version) {
|
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) {
|
for (const asgArn of nonCompliantResources) {
|
||||||
const asgName = asgArn.split(':').pop()!;
|
const asgName = asgArn.split(':').pop()!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateAutoScalingGroupCommand({
|
new UpdateAutoScalingGroupCommand({
|
||||||
AutoScalingGroupName: asgName,
|
AutoScalingGroupName: asgName,
|
||||||
LaunchTemplate: {
|
LaunchTemplate: {
|
||||||
LaunchTemplateId: launchTemplateId,
|
LaunchTemplateId: launchTemplateId,
|
||||||
Version: version,
|
Version: version
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
AutoScalingClient,
|
AutoScalingClient,
|
||||||
DescribeAutoScalingGroupsCommand,
|
DescribeAutoScalingGroupsCommand,
|
||||||
UpdateAutoScalingGroupCommand,
|
UpdateAutoScalingGroupCommand
|
||||||
} from '@aws-sdk/client-auto-scaling';
|
} from '@aws-sdk/client-auto-scaling'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class AutoScalingMultipleAZ implements BPSet {
|
export class AutoScalingMultipleAZ implements BPSet {
|
||||||
private readonly client = new AutoScalingClient({});
|
private readonly client = new AutoScalingClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getAutoScalingGroups = async () => {
|
private readonly getAutoScalingGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeAutoScalingGroupsCommand({}))
|
||||||
return response.AutoScalingGroups || [];
|
return response.AutoScalingGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'AutoScalingMultipleAZ',
|
name: 'AutoScalingMultipleAZ',
|
||||||
@ -28,103 +28,102 @@ export class AutoScalingMultipleAZ implements BPSet {
|
|||||||
name: 'availability-zones',
|
name: 'availability-zones',
|
||||||
description: 'Comma-separated list of Availability Zones to assign to the Auto Scaling group.',
|
description: 'Comma-separated list of Availability Zones to assign to the Auto Scaling group.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'us-east-1a,us-east-1b',
|
example: 'us-east-1a,us-east-1b'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeAutoScalingGroupsCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateAutoScalingGroupCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const asgs = await this.getAutoScalingGroups();
|
const asgs = await this.getAutoScalingGroups()
|
||||||
|
|
||||||
for (const asg of asgs) {
|
for (const asg of asgs) {
|
||||||
if (asg.AvailabilityZones?.length! > 1) {
|
if ((asg.AvailabilityZones?.length ?? 0) > 1) {
|
||||||
compliantResources.push(asg.AutoScalingGroupARN!);
|
compliantResources.push(asg.AutoScalingGroupARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(asg.AutoScalingGroupARN!);
|
nonCompliantResources.push(asg.AutoScalingGroupARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const availabilityZones = requiredParametersForFix.find(
|
const availabilityZones = requiredParametersForFix.find((param) => param.name === 'availability-zones')?.value
|
||||||
(param) => param.name === 'availability-zones'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!availabilityZones) {
|
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) {
|
for (const asgArn of nonCompliantResources) {
|
||||||
const asgName = asgArn.split(':').pop()!;
|
const asgName = asgArn.split(':').pop()!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateAutoScalingGroupCommand({
|
new UpdateAutoScalingGroupCommand({
|
||||||
AutoScalingGroupName: asgName,
|
AutoScalingGroupName: asgName,
|
||||||
AvailabilityZones: availabilityZones.split(','),
|
AvailabilityZones: availabilityZones.split(',')
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,27 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand,
|
UpdateDistributionCommand
|
||||||
} from '@aws-sdk/client-cloudfront';
|
} from '@aws-sdk/client-cloudfront'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CloudFrontAccessLogsEnabled implements BPSet {
|
export class CloudFrontAccessLogsEnabled implements BPSet {
|
||||||
private readonly client = new CloudFrontClient({});
|
private readonly client = new CloudFrontClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getDistributions = async () => {
|
private readonly getDistributions = async () => {
|
||||||
const response = await this.memoClient.send(new ListDistributionsCommand({}));
|
const response = await this.memoClient.send(new ListDistributionsCommand({}))
|
||||||
return response.DistributionList?.Items || [];
|
return response.DistributionList?.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDistributionDetails = async (distributionId: string) => {
|
private readonly getDistributionDetails = async (distributionId: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
|
||||||
new GetDistributionCommand({ Id: distributionId })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
distribution: response.Distribution!,
|
distribution: response.Distribution!,
|
||||||
etag: response.ETag!,
|
etag: response.ETag!
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CloudFrontAccessLogsEnabled',
|
name: 'CloudFrontAccessLogsEnabled',
|
||||||
@ -39,103 +37,101 @@ export class CloudFrontAccessLogsEnabled implements BPSet {
|
|||||||
name: 'log-bucket-name',
|
name: 'log-bucket-name',
|
||||||
description: 'The S3 bucket name for storing access logs.',
|
description: 'The S3 bucket name for storing access logs.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'my-cloudfront-logs-bucket.s3.amazonaws.com',
|
example: 'my-cloudfront-logs-bucket.s3.amazonaws.com'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListDistributionsCommand',
|
name: 'ListDistributionsCommand',
|
||||||
reason: 'List all CloudFront distributions.',
|
reason: 'List all CloudFront distributions.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GetDistributionCommand',
|
name: 'GetDistributionCommand',
|
||||||
reason: 'Retrieve distribution details to check logging settings.',
|
reason: 'Retrieve distribution details to check logging settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateDistributionCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const distributions = await this.getDistributions();
|
const distributions = await this.getDistributions()
|
||||||
|
|
||||||
for (const distribution of distributions) {
|
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) {
|
if (details.DistributionConfig?.Logging?.Enabled) {
|
||||||
compliantResources.push(details.ARN!);
|
compliantResources.push(details.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(details.ARN!);
|
nonCompliantResources.push(details.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const logBucketName = requiredParametersForFix.find(
|
const logBucketName = requiredParametersForFix.find((param) => param.name === 'log-bucket-name')?.value
|
||||||
(param) => param.name === 'log-bucket-name'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!logBucketName) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const distributionId = arn.split('/').pop()!;
|
const distributionId = arn.split('/').pop()!
|
||||||
const { distribution, etag } = await this.getDistributionDetails(distributionId);
|
const { distribution, etag } = await this.getDistributionDetails(distributionId)
|
||||||
|
|
||||||
const updatedConfig = {
|
const updatedConfig = {
|
||||||
...distribution.DistributionConfig,
|
...distribution.DistributionConfig,
|
||||||
@ -143,17 +139,17 @@ export class CloudFrontAccessLogsEnabled implements BPSet {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
Bucket: logBucketName,
|
Bucket: logBucketName,
|
||||||
IncludeCookies: false,
|
IncludeCookies: false,
|
||||||
Prefix: '',
|
Prefix: ''
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as any,
|
DistributionConfig: updatedConfig as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,27 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand,
|
UpdateDistributionCommand
|
||||||
} from '@aws-sdk/client-cloudfront';
|
} from '@aws-sdk/client-cloudfront'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CloudFrontAssociatedWithWAF implements BPSet {
|
export class CloudFrontAssociatedWithWAF implements BPSet {
|
||||||
private readonly client = new CloudFrontClient({});
|
private readonly client = new CloudFrontClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getDistributions = async () => {
|
private readonly getDistributions = async () => {
|
||||||
const response = await this.memoClient.send(new ListDistributionsCommand({}));
|
const response = await this.memoClient.send(new ListDistributionsCommand({}))
|
||||||
return response.DistributionList?.Items || [];
|
return response.DistributionList?.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDistributionDetails = async (distributionId: string) => {
|
private readonly getDistributionDetails = async (distributionId: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
|
||||||
new GetDistributionCommand({ Id: distributionId })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
distribution: response.Distribution!,
|
distribution: response.Distribution!,
|
||||||
etag: response.ETag!,
|
etag: response.ETag!
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CloudFrontAssociatedWithWAF',
|
name: 'CloudFrontAssociatedWithWAF',
|
||||||
@ -39,111 +37,109 @@ export class CloudFrontAssociatedWithWAF implements BPSet {
|
|||||||
name: 'web-acl-id',
|
name: 'web-acl-id',
|
||||||
description: 'The ID of the Web ACL to associate with the CloudFront distribution.',
|
description: 'The ID of the Web ACL to associate with the CloudFront distribution.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListDistributionsCommand',
|
name: 'ListDistributionsCommand',
|
||||||
reason: 'List all CloudFront distributions to check for WAF association.',
|
reason: 'List all CloudFront distributions to check for WAF association.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateDistributionCommand',
|
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 application’s security requirements.',
|
adviseBeforeFixFunction: 'Ensure the Web ACL is configured correctly for the application’s security requirements.'
|
||||||
});
|
})
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const distributions = await this.getDistributions();
|
const distributions = await this.getDistributions()
|
||||||
|
|
||||||
for (const distribution of distributions) {
|
for (const distribution of distributions) {
|
||||||
if (distribution.WebACLId && distribution.WebACLId !== '') {
|
if (distribution.WebACLId && distribution.WebACLId !== '') {
|
||||||
compliantResources.push(distribution.ARN!);
|
compliantResources.push(distribution.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(distribution.ARN!);
|
nonCompliantResources.push(distribution.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const webAclId = requiredParametersForFix.find(
|
const webAclId = requiredParametersForFix.find((param) => param.name === 'web-acl-id')?.value
|
||||||
(param) => param.name === 'web-acl-id'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!webAclId) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const distributionId = arn.split('/').pop()!;
|
const distributionId = arn.split('/').pop()!
|
||||||
const { distribution, etag } = await this.getDistributionDetails(distributionId);
|
const { distribution, etag } = await this.getDistributionDetails(distributionId)
|
||||||
|
|
||||||
const updatedConfig = {
|
const updatedConfig = {
|
||||||
...distribution.DistributionConfig,
|
...distribution.DistributionConfig,
|
||||||
WebACLId: webAclId,
|
WebACLId: webAclId
|
||||||
};
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as any,
|
DistributionConfig: updatedConfig as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,34 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand,
|
UpdateDistributionCommand
|
||||||
} from '@aws-sdk/client-cloudfront';
|
} from '@aws-sdk/client-cloudfront'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CloudFrontDefaultRootObjectConfigured implements BPSet {
|
export class CloudFrontDefaultRootObjectConfigured implements BPSet {
|
||||||
private readonly client = new CloudFrontClient({});
|
private readonly client = new CloudFrontClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getDistributions = async () => {
|
private readonly getDistributions = async () => {
|
||||||
const response = await this.memoClient.send(new ListDistributionsCommand({}));
|
const response = await this.memoClient.send(new ListDistributionsCommand({}))
|
||||||
return response.DistributionList?.Items || [];
|
return response.DistributionList?.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDistributionDetails = async (distributionId: string) => {
|
private readonly getDistributionDetails = async (distributionId: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
|
||||||
new GetDistributionCommand({ Id: distributionId })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
distribution: response.Distribution!,
|
distribution: response.Distribution!,
|
||||||
etag: response.ETag!,
|
etag: response.ETag!
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CloudFrontDefaultRootObjectConfigured',
|
name: 'CloudFrontDefaultRootObjectConfigured',
|
||||||
description: 'Ensures that CloudFront distributions have a default root object configured.',
|
description: 'Ensures that CloudFront distributions have a default root object configured.',
|
||||||
priority: 3,
|
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',
|
awsService: 'CloudFront',
|
||||||
awsServiceCategory: 'CDN',
|
awsServiceCategory: 'CDN',
|
||||||
bestPracticeCategory: 'Configuration',
|
bestPracticeCategory: 'Configuration',
|
||||||
@ -39,116 +38,114 @@ export class CloudFrontDefaultRootObjectConfigured implements BPSet {
|
|||||||
name: 'default-root-object',
|
name: 'default-root-object',
|
||||||
description: 'The default root object for the CloudFront distribution.',
|
description: 'The default root object for the CloudFront distribution.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'index.html',
|
example: 'index.html'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListDistributionsCommand',
|
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',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateDistributionCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const distributions = await this.getDistributions();
|
const distributions = await this.getDistributions()
|
||||||
|
|
||||||
for (const distribution of distributions) {
|
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 !== '') {
|
if (details.DistributionConfig?.DefaultRootObject !== '') {
|
||||||
compliantResources.push(details.ARN!);
|
compliantResources.push(details.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(details.ARN!);
|
nonCompliantResources.push(details.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const defaultRootObject = requiredParametersForFix.find(
|
const defaultRootObject = requiredParametersForFix.find((param) => param.name === 'default-root-object')?.value
|
||||||
(param) => param.name === 'default-root-object'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!defaultRootObject) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const distributionId = arn.split('/').pop()!;
|
const distributionId = arn.split('/').pop()!
|
||||||
const { distribution, etag } = await this.getDistributionDetails(distributionId);
|
const { distribution, etag } = await this.getDistributionDetails(distributionId)
|
||||||
|
|
||||||
const updatedConfig = {
|
const updatedConfig = {
|
||||||
...distribution.DistributionConfig,
|
...distribution.DistributionConfig,
|
||||||
DefaultRootObject: defaultRootObject,
|
DefaultRootObject: defaultRootObject
|
||||||
};
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as any,
|
DistributionConfig: updatedConfig as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,27 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand,
|
UpdateDistributionCommand
|
||||||
} from '@aws-sdk/client-cloudfront';
|
} from '@aws-sdk/client-cloudfront'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
|
export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
|
||||||
private readonly client = new CloudFrontClient({});
|
private readonly client = new CloudFrontClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getDistributions = async () => {
|
private readonly getDistributions = async () => {
|
||||||
const response = await this.memoClient.send(new ListDistributionsCommand({}));
|
const response = await this.memoClient.send(new ListDistributionsCommand({}))
|
||||||
return response.DistributionList?.Items || [];
|
return response.DistributionList?.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDistributionDetails = async (distributionId: string) => {
|
private readonly getDistributionDetails = async (distributionId: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
|
||||||
new GetDistributionCommand({ Id: distributionId })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
distribution: response.Distribution!,
|
distribution: response.Distribution!,
|
||||||
etag: response.ETag!,
|
etag: response.ETag!
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CloudFrontNoDeprecatedSSLProtocols',
|
name: 'CloudFrontNoDeprecatedSSLProtocols',
|
||||||
@ -39,94 +37,92 @@ export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListDistributionsCommand',
|
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',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateDistributionCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const distributions = await this.getDistributions();
|
const distributions = await this.getDistributions()
|
||||||
|
|
||||||
for (const distribution of distributions) {
|
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(
|
const hasDeprecatedSSL = details.DistributionConfig?.Origins?.Items?.some(
|
||||||
(origin) =>
|
(origin) => origin.CustomOriginConfig && origin.CustomOriginConfig.OriginSslProtocols?.Items?.includes('SSLv3')
|
||||||
origin.CustomOriginConfig &&
|
)
|
||||||
origin.CustomOriginConfig.OriginSslProtocols?.Items?.includes('SSLv3')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasDeprecatedSSL) {
|
if (hasDeprecatedSSL) {
|
||||||
nonCompliantResources.push(details.ARN!);
|
nonCompliantResources.push(details.ARN!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(details.ARN!);
|
compliantResources.push(details.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const distributionId = arn.split('/').pop()!;
|
const distributionId = arn.split('/').pop()!
|
||||||
const { distribution, etag } = await this.getDistributionDetails(distributionId);
|
const { distribution, etag } = await this.getDistributionDetails(distributionId)
|
||||||
|
|
||||||
const updatedConfig = {
|
const updatedConfig = {
|
||||||
...distribution.DistributionConfig,
|
...distribution.DistributionConfig,
|
||||||
@ -141,23 +137,23 @@ export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
|
|||||||
...origin.CustomOriginConfig.OriginSslProtocols,
|
...origin.CustomOriginConfig.OriginSslProtocols,
|
||||||
Items: origin.CustomOriginConfig.OriginSslProtocols?.Items?.filter(
|
Items: origin.CustomOriginConfig.OriginSslProtocols?.Items?.filter(
|
||||||
(protocol) => protocol !== 'SSLv3'
|
(protocol) => protocol !== 'SSLv3'
|
||||||
),
|
)
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
return origin;
|
return origin
|
||||||
}),
|
})
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as any,
|
DistributionConfig: updatedConfig as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,34 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand,
|
UpdateDistributionCommand
|
||||||
} from '@aws-sdk/client-cloudfront';
|
} from '@aws-sdk/client-cloudfront'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
|
export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
|
||||||
private readonly client = new CloudFrontClient({});
|
private readonly client = new CloudFrontClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getDistributions = async () => {
|
private readonly getDistributions = async () => {
|
||||||
const response = await this.memoClient.send(new ListDistributionsCommand({}));
|
const response = await this.memoClient.send(new ListDistributionsCommand({}))
|
||||||
return response.DistributionList?.Items || [];
|
return response.DistributionList?.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDistributionDetails = async (distributionId: string) => {
|
private readonly getDistributionDetails = async (distributionId: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
|
||||||
new GetDistributionCommand({ Id: distributionId })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
distribution: response.Distribution!,
|
distribution: response.Distribution!,
|
||||||
etag: response.ETag!,
|
etag: response.ETag!
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CloudFrontS3OriginAccessControlEnabled',
|
name: 'CloudFrontS3OriginAccessControlEnabled',
|
||||||
description: 'Ensures that CloudFront distributions with S3 origins have Origin Access Control (OAC) enabled.',
|
description: 'Ensures that CloudFront distributions with S3 origins have Origin Access Control (OAC) enabled.',
|
||||||
priority: 3,
|
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',
|
awsService: 'CloudFront',
|
||||||
awsServiceCategory: 'CDN',
|
awsServiceCategory: 'CDN',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -39,109 +38,108 @@ export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
|
|||||||
name: 'origin-access-control-id',
|
name: 'origin-access-control-id',
|
||||||
description: 'The ID of the Origin Access Control to associate with the S3 origin.',
|
description: 'The ID of the Origin Access Control to associate with the S3 origin.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'oac-0abcd1234efgh5678',
|
example: 'oac-0abcd1234efgh5678'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListDistributionsCommand',
|
name: 'ListDistributionsCommand',
|
||||||
reason: 'List all CloudFront distributions to check for S3 origins.',
|
reason: 'List all CloudFront distributions to check for S3 origins.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GetDistributionCommand',
|
name: 'GetDistributionCommand',
|
||||||
reason: 'Retrieve distribution details to verify Origin Access Control configuration.',
|
reason: 'Retrieve distribution details to verify Origin Access Control configuration.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateDistributionCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const distributions = await this.getDistributions();
|
const distributions = await this.getDistributions()
|
||||||
|
|
||||||
for (const distribution of distributions) {
|
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(
|
const hasNonCompliantOrigin = details.DistributionConfig?.Origins?.Items?.some(
|
||||||
(origin) =>
|
(origin) => origin.S3OriginConfig && (!origin.OriginAccessControlId || origin.OriginAccessControlId === '')
|
||||||
origin.S3OriginConfig &&
|
)
|
||||||
(!origin.OriginAccessControlId || origin.OriginAccessControlId === '')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasNonCompliantOrigin) {
|
if (hasNonCompliantOrigin) {
|
||||||
nonCompliantResources.push(details.ARN!);
|
nonCompliantResources.push(details.ARN!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(details.ARN!);
|
compliantResources.push(details.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const originAccessControlId = requiredParametersForFix.find(
|
const originAccessControlId = requiredParametersForFix.find(
|
||||||
(param) => param.name === 'origin-access-control-id'
|
(param) => param.name === 'origin-access-control-id'
|
||||||
)?.value;
|
)?.value
|
||||||
|
|
||||||
if (!originAccessControlId) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const distributionId = arn.split('/').pop()!;
|
const distributionId = arn.split('/').pop()!
|
||||||
const { distribution, etag } = await this.getDistributionDetails(distributionId);
|
const { distribution, etag } = await this.getDistributionDetails(distributionId)
|
||||||
|
|
||||||
const updatedConfig = {
|
const updatedConfig = {
|
||||||
...distribution.DistributionConfig,
|
...distribution.DistributionConfig,
|
||||||
@ -150,21 +148,21 @@ export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
|
|||||||
if (origin.S3OriginConfig) {
|
if (origin.S3OriginConfig) {
|
||||||
return {
|
return {
|
||||||
...origin,
|
...origin,
|
||||||
OriginAccessControlId: originAccessControlId,
|
OriginAccessControlId: originAccessControlId
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
return origin;
|
return origin
|
||||||
}),
|
})
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as any,
|
DistributionConfig: updatedConfig as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,34 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand,
|
UpdateDistributionCommand
|
||||||
} from '@aws-sdk/client-cloudfront';
|
} from '@aws-sdk/client-cloudfront'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CloudFrontViewerPolicyHTTPS implements BPSet {
|
export class CloudFrontViewerPolicyHTTPS implements BPSet {
|
||||||
private readonly client = new CloudFrontClient({});
|
private readonly client = new CloudFrontClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getDistributions = async () => {
|
private readonly getDistributions = async () => {
|
||||||
const response = await this.memoClient.send(new ListDistributionsCommand({}));
|
const response = await this.memoClient.send(new ListDistributionsCommand({}))
|
||||||
return response.DistributionList?.Items || [];
|
return response.DistributionList?.Items || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDistributionDetails = async (distributionId: string) => {
|
private readonly getDistributionDetails = async (distributionId: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetDistributionCommand({ Id: distributionId }))
|
||||||
new GetDistributionCommand({ Id: distributionId })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
distribution: response.Distribution!,
|
distribution: response.Distribution!,
|
||||||
etag: response.ETag!,
|
etag: response.ETag!
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CloudFrontViewerPolicyHTTPS',
|
name: 'CloudFrontViewerPolicyHTTPS',
|
||||||
description: 'Ensures that CloudFront distributions enforce HTTPS for viewer requests.',
|
description: 'Ensures that CloudFront distributions enforce HTTPS for viewer requests.',
|
||||||
priority: 1,
|
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',
|
awsService: 'CloudFront',
|
||||||
awsServiceCategory: 'CDN',
|
awsServiceCategory: 'CDN',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -39,116 +38,116 @@ export class CloudFrontViewerPolicyHTTPS implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListDistributionsCommand',
|
name: 'ListDistributionsCommand',
|
||||||
reason: 'List all CloudFront distributions to check viewer protocol policies.',
|
reason: 'List all CloudFront distributions to check viewer protocol policies.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GetDistributionCommand',
|
name: 'GetDistributionCommand',
|
||||||
reason: 'Retrieve distribution details to verify viewer protocol policy settings.',
|
reason: 'Retrieve distribution details to verify viewer protocol policy settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateDistributionCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const distributions = await this.getDistributions();
|
const distributions = await this.getDistributions()
|
||||||
|
|
||||||
for (const distribution of distributions) {
|
for (const distribution of distributions) {
|
||||||
const { distribution: details } = await this.getDistributionDetails(distribution.Id!);
|
const { distribution: details } = await this.getDistributionDetails(distribution.Id!)
|
||||||
const hasNonCompliantViewerPolicy =
|
const hasNonCompliantViewerPolicy =
|
||||||
details.DistributionConfig?.DefaultCacheBehavior?.ViewerProtocolPolicy === 'allow-all' ||
|
details.DistributionConfig?.DefaultCacheBehavior?.ViewerProtocolPolicy === 'allow-all' ||
|
||||||
details.DistributionConfig?.CacheBehaviors?.Items?.some(
|
details.DistributionConfig?.CacheBehaviors?.Items?.some(
|
||||||
(behavior) => behavior.ViewerProtocolPolicy === 'allow-all'
|
(behavior) => behavior.ViewerProtocolPolicy === 'allow-all'
|
||||||
);
|
)
|
||||||
|
|
||||||
if (hasNonCompliantViewerPolicy) {
|
if (hasNonCompliantViewerPolicy) {
|
||||||
nonCompliantResources.push(details.ARN!);
|
nonCompliantResources.push(details.ARN!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(details.ARN!);
|
compliantResources.push(details.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const distributionId = arn.split('/').pop()!;
|
const distributionId = arn.split('/').pop()!
|
||||||
const { distribution, etag } = await this.getDistributionDetails(distributionId);
|
const { distribution, etag } = await this.getDistributionDetails(distributionId)
|
||||||
|
|
||||||
const updatedConfig = {
|
const updatedConfig = {
|
||||||
...distribution.DistributionConfig,
|
...distribution.DistributionConfig,
|
||||||
DefaultCacheBehavior: {
|
DefaultCacheBehavior: {
|
||||||
...distribution.DistributionConfig?.DefaultCacheBehavior,
|
...distribution.DistributionConfig?.DefaultCacheBehavior,
|
||||||
ViewerProtocolPolicy: 'redirect-to-https',
|
ViewerProtocolPolicy: 'redirect-to-https'
|
||||||
},
|
},
|
||||||
CacheBehaviors: {
|
CacheBehaviors: {
|
||||||
Items: distribution.DistributionConfig?.CacheBehaviors?.Items?.map((behavior) => ({
|
Items: distribution.DistributionConfig?.CacheBehaviors?.Items?.map((behavior) => ({
|
||||||
...behavior,
|
...behavior,
|
||||||
ViewerProtocolPolicy: 'redirect-to-https',
|
ViewerProtocolPolicy: 'redirect-to-https'
|
||||||
})),
|
}))
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as any,
|
DistributionConfig: updatedConfig as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
CloudWatchLogsClient,
|
CloudWatchLogsClient,
|
||||||
DescribeLogGroupsCommand,
|
DescribeLogGroupsCommand,
|
||||||
PutRetentionPolicyCommand,
|
PutRetentionPolicyCommand
|
||||||
} from '@aws-sdk/client-cloudwatch-logs';
|
} from '@aws-sdk/client-cloudwatch-logs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CWLogGroupRetentionPeriodCheck implements BPSet {
|
export class CWLogGroupRetentionPeriodCheck implements BPSet {
|
||||||
private readonly client = new CloudWatchLogsClient({});
|
private readonly client = new CloudWatchLogsClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getLogGroups = async () => {
|
private readonly getLogGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeLogGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeLogGroupsCommand({}))
|
||||||
return response.logGroups || [];
|
return response.logGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CWLogGroupRetentionPeriodCheck',
|
name: 'CWLogGroupRetentionPeriodCheck',
|
||||||
@ -28,103 +28,101 @@ export class CWLogGroupRetentionPeriodCheck implements BPSet {
|
|||||||
name: 'retention-period-days',
|
name: 'retention-period-days',
|
||||||
description: 'Retention period in days to apply to log groups.',
|
description: 'Retention period in days to apply to log groups.',
|
||||||
default: '',
|
default: '',
|
||||||
example: '30',
|
example: '30'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeLogGroupsCommand',
|
name: 'DescribeLogGroupsCommand',
|
||||||
reason: 'Retrieve all CloudWatch log groups to verify retention settings.',
|
reason: 'Retrieve all CloudWatch log groups to verify retention settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutRetentionPolicyCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const logGroups = await this.getLogGroups();
|
const logGroups = await this.getLogGroups()
|
||||||
|
|
||||||
for (const logGroup of logGroups) {
|
for (const logGroup of logGroups) {
|
||||||
if (logGroup.retentionInDays) {
|
if (logGroup.retentionInDays) {
|
||||||
compliantResources.push(logGroup.logGroupArn!);
|
compliantResources.push(logGroup.logGroupArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(logGroup.logGroupArn!);
|
nonCompliantResources.push(logGroup.logGroupArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const retentionPeriod = requiredParametersForFix.find(
|
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'retention-period-days')?.value
|
||||||
(param) => param.name === 'retention-period-days'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!retentionPeriod) {
|
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) {
|
for (const logGroupArn of nonCompliantResources) {
|
||||||
const logGroupName = logGroupArn.split(':').pop()!;
|
const logGroupName = logGroupArn.split(':').pop()!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutRetentionPolicyCommand({
|
new PutRetentionPolicyCommand({
|
||||||
logGroupName,
|
logGroupName,
|
||||||
retentionInDays: parseInt(retentionPeriod, 10),
|
retentionInDays: parseInt(retentionPeriod, 10)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
import {
|
import { CloudWatchClient, DescribeAlarmsCommand, PutMetricAlarmCommand } from '@aws-sdk/client-cloudwatch'
|
||||||
CloudWatchClient,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeAlarmsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
PutMetricAlarmCommand,
|
|
||||||
} from '@aws-sdk/client-cloudwatch';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class CloudWatchAlarmSettingsCheck implements BPSet {
|
export class CloudWatchAlarmSettingsCheck implements BPSet {
|
||||||
private readonly client = new CloudWatchClient({});
|
private readonly client = new CloudWatchClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getAlarms = async () => {
|
private readonly getAlarms = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeAlarmsCommand({}));
|
const response = await this.memoClient.send(new DescribeAlarmsCommand({}))
|
||||||
return response.MetricAlarms || [];
|
return response.MetricAlarms || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CloudWatchAlarmSettingsCheck',
|
name: 'CloudWatchAlarmSettingsCheck',
|
||||||
@ -26,112 +22,120 @@ export class CloudWatchAlarmSettingsCheck implements BPSet {
|
|||||||
requiredParametersForFix: [
|
requiredParametersForFix: [
|
||||||
{ name: 'metric-name', description: 'The metric name for the alarm.', default: '', example: 'CPUUtilization' },
|
{ 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: '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: '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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeAlarmsCommand',
|
name: 'DescribeAlarmsCommand',
|
||||||
reason: 'Retrieve all CloudWatch alarms to verify their settings.',
|
reason: 'Retrieve all CloudWatch alarms to verify their settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutMetricAlarmCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const alarms = await this.getAlarms();
|
const alarms = await this.getAlarms()
|
||||||
const parameters = {
|
const parameters = {
|
||||||
MetricName: '', // Required
|
MetricName: '', // Required
|
||||||
Threshold: null,
|
Threshold: null,
|
||||||
EvaluationPeriods: null,
|
EvaluationPeriods: null,
|
||||||
Period: null,
|
Period: null,
|
||||||
ComparisonOperator: null,
|
ComparisonOperator: null,
|
||||||
Statistic: null,
|
Statistic: null
|
||||||
};
|
}
|
||||||
|
|
||||||
for (const alarm of alarms) {
|
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]) {
|
if (alarm[key as keyof typeof alarm] !== parameters[key as keyof typeof parameters]) {
|
||||||
isCompliant = false;
|
isCompliant = false
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCompliant) {
|
if (isCompliant) {
|
||||||
compliantResources.push(alarm.AlarmArn!);
|
compliantResources.push(alarm.AlarmArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(alarm.AlarmArn!);
|
nonCompliantResources.push(alarm.AlarmArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const requiredSettings = Object.fromEntries(
|
const requiredSettings = Object.fromEntries(requiredParametersForFix.map((param) => [param.name, param.value]))
|
||||||
requiredParametersForFix.map((param) => [param.name, param.value])
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const alarmArn of nonCompliantResources) {
|
for (const alarmArn of nonCompliantResources) {
|
||||||
const alarmName = alarmArn.split(':').pop()!;
|
const alarmName = alarmArn.split(':').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutMetricAlarmCommand({
|
new PutMetricAlarmCommand({
|
||||||
@ -140,10 +144,10 @@ export class CloudWatchAlarmSettingsCheck implements BPSet {
|
|||||||
Threshold: parseFloat(requiredSettings['threshold']),
|
Threshold: parseFloat(requiredSettings['threshold']),
|
||||||
EvaluationPeriods: parseInt(requiredSettings['evaluation-periods'], 10),
|
EvaluationPeriods: parseInt(requiredSettings['evaluation-periods'], 10),
|
||||||
Period: parseInt(requiredSettings['period'], 10),
|
Period: parseInt(requiredSettings['period'], 10),
|
||||||
ComparisonOperator: requiredSettings['comparison-operator'] as any,
|
ComparisonOperator: requiredSettings['comparison-operator'] as unknown,
|
||||||
Statistic: requiredSettings['statistic'] as any,
|
Statistic: requiredSettings['statistic'] as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,25 +2,23 @@ import {
|
|||||||
CodeBuildClient,
|
CodeBuildClient,
|
||||||
ListProjectsCommand,
|
ListProjectsCommand,
|
||||||
BatchGetProjectsCommand,
|
BatchGetProjectsCommand,
|
||||||
UpdateProjectCommand,
|
UpdateProjectCommand
|
||||||
} from '@aws-sdk/client-codebuild';
|
} from '@aws-sdk/client-codebuild'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
|
export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
|
||||||
private readonly client = new CodeBuildClient({});
|
private readonly client = new CodeBuildClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getProjects = async () => {
|
private readonly getProjects = async () => {
|
||||||
const projectNames = await this.memoClient.send(new ListProjectsCommand({}));
|
const projectNames = await this.memoClient.send(new ListProjectsCommand({}))
|
||||||
if (!projectNames.projects?.length) {
|
if (!projectNames.projects?.length) {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new BatchGetProjectsCommand({ names: projectNames.projects }))
|
||||||
new BatchGetProjectsCommand({ names: projectNames.projects })
|
return response.projects || []
|
||||||
);
|
}
|
||||||
return response.projects || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CodeBuildProjectEnvironmentPrivilegedCheck',
|
name: 'CodeBuildProjectEnvironmentPrivilegedCheck',
|
||||||
@ -35,92 +33,92 @@ export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListProjectsCommand',
|
name: 'ListProjectsCommand',
|
||||||
reason: 'Retrieve all CodeBuild projects to verify environment settings.',
|
reason: 'Retrieve all CodeBuild projects to verify environment settings.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'BatchGetProjectsCommand',
|
name: 'BatchGetProjectsCommand',
|
||||||
reason: 'Fetch detailed configuration for each CodeBuild project.',
|
reason: 'Fetch detailed configuration for each CodeBuild project.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateProjectCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const projects = await this.getProjects();
|
const projects = await this.getProjects()
|
||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
if (!project.environment?.privilegedMode) {
|
if (!project.environment?.privilegedMode) {
|
||||||
compliantResources.push(project.arn!);
|
compliantResources.push(project.arn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(project.arn!);
|
nonCompliantResources.push(project.arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
const projects = await this.getProjects();
|
const projects = await this.getProjects()
|
||||||
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const projectName = arn.split(':').pop()!;
|
const projectName = arn.split(':').pop()!
|
||||||
const projectToFix = projects.find((project) => project.arn === arn);
|
const projectToFix = projects.find((project) => project.arn === arn)
|
||||||
|
|
||||||
if (!projectToFix) {
|
if (!projectToFix) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
@ -128,10 +126,10 @@ export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
|
|||||||
name: projectName,
|
name: projectName,
|
||||||
environment: {
|
environment: {
|
||||||
...projectToFix.environment,
|
...projectToFix.environment,
|
||||||
privilegedMode: false,
|
privilegedMode: false
|
||||||
} as any,
|
} as unknown
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,25 +2,23 @@ import {
|
|||||||
CodeBuildClient,
|
CodeBuildClient,
|
||||||
ListProjectsCommand,
|
ListProjectsCommand,
|
||||||
BatchGetProjectsCommand,
|
BatchGetProjectsCommand,
|
||||||
UpdateProjectCommand,
|
UpdateProjectCommand
|
||||||
} from '@aws-sdk/client-codebuild';
|
} from '@aws-sdk/client-codebuild'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CodeBuildProjectLoggingEnabled implements BPSet {
|
export class CodeBuildProjectLoggingEnabled implements BPSet {
|
||||||
private readonly client = new CodeBuildClient({});
|
private readonly client = new CodeBuildClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getProjects = async () => {
|
private readonly getProjects = async () => {
|
||||||
const projectNames = await this.memoClient.send(new ListProjectsCommand({}));
|
const projectNames = await this.memoClient.send(new ListProjectsCommand({}))
|
||||||
if (!projectNames.projects?.length) {
|
if (!projectNames.projects?.length) {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new BatchGetProjectsCommand({ names: projectNames.projects }))
|
||||||
new BatchGetProjectsCommand({ names: projectNames.projects })
|
return response.projects || []
|
||||||
);
|
}
|
||||||
return response.projects || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CodeBuildProjectLoggingEnabled',
|
name: 'CodeBuildProjectLoggingEnabled',
|
||||||
@ -35,96 +33,93 @@ export class CodeBuildProjectLoggingEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListProjectsCommand',
|
name: 'ListProjectsCommand',
|
||||||
reason: 'Retrieve all CodeBuild projects to verify logging settings.',
|
reason: 'Retrieve all CodeBuild projects to verify logging settings.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'BatchGetProjectsCommand',
|
name: 'BatchGetProjectsCommand',
|
||||||
reason: 'Fetch detailed configuration for each CodeBuild project.',
|
reason: 'Fetch detailed configuration for each CodeBuild project.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateProjectCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const projects = await this.getProjects();
|
const projects = await this.getProjects()
|
||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
const logsConfig = project.logsConfig;
|
const logsConfig = project.logsConfig
|
||||||
if (
|
if (logsConfig?.cloudWatchLogs?.status === 'ENABLED' || logsConfig?.s3Logs?.status === 'ENABLED') {
|
||||||
logsConfig?.cloudWatchLogs?.status === 'ENABLED' ||
|
compliantResources.push(project.arn!)
|
||||||
logsConfig?.s3Logs?.status === 'ENABLED'
|
|
||||||
) {
|
|
||||||
compliantResources.push(project.arn!);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(project.arn!);
|
nonCompliantResources.push(project.arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
const projects = await this.getProjects();
|
const projects = await this.getProjects()
|
||||||
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const projectName = arn.split(':').pop()!;
|
const projectName = arn.split(':').pop()!
|
||||||
const projectToFix = projects.find((project) => project.arn === arn);
|
const projectToFix = projects.find((project) => project.arn === arn)
|
||||||
|
|
||||||
if (!projectToFix) {
|
if (!projectToFix) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
@ -135,11 +130,11 @@ export class CodeBuildProjectLoggingEnabled implements BPSet {
|
|||||||
cloudWatchLogs: {
|
cloudWatchLogs: {
|
||||||
status: 'ENABLED',
|
status: 'ENABLED',
|
||||||
groupName: 'default-cloudwatch-group',
|
groupName: 'default-cloudwatch-group',
|
||||||
streamName: 'default-stream',
|
streamName: 'default-stream'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,43 +3,44 @@ import {
|
|||||||
ListApplicationsCommand,
|
ListApplicationsCommand,
|
||||||
ListDeploymentGroupsCommand,
|
ListDeploymentGroupsCommand,
|
||||||
BatchGetDeploymentGroupsCommand,
|
BatchGetDeploymentGroupsCommand,
|
||||||
UpdateDeploymentGroupCommand,
|
UpdateDeploymentGroupCommand
|
||||||
} from '@aws-sdk/client-codedeploy';
|
} from '@aws-sdk/client-codedeploy'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
|
export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
|
||||||
private readonly client = new CodeDeployClient({});
|
private readonly client = new CodeDeployClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getDeploymentGroups = async () => {
|
private readonly getDeploymentGroups = async () => {
|
||||||
const applications = await this.memoClient.send(new ListApplicationsCommand({}));
|
const applications = await this.memoClient.send(new ListApplicationsCommand({}))
|
||||||
const deploymentGroupsInfo = [];
|
const deploymentGroupsInfo = []
|
||||||
|
|
||||||
for (const application of applications.applications || []) {
|
for (const application of applications.applications || []) {
|
||||||
const deploymentGroups = await this.memoClient.send(
|
const deploymentGroups = await this.memoClient.send(
|
||||||
new ListDeploymentGroupsCommand({ applicationName: application })
|
new ListDeploymentGroupsCommand({ applicationName: application })
|
||||||
);
|
)
|
||||||
if (!deploymentGroups.deploymentGroups?.length) {
|
if (!deploymentGroups.deploymentGroups?.length) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
const batchResponse = await this.memoClient.send(
|
const batchResponse = await this.memoClient.send(
|
||||||
new BatchGetDeploymentGroupsCommand({
|
new BatchGetDeploymentGroupsCommand({
|
||||||
applicationName: application,
|
applicationName: application,
|
||||||
deploymentGroupNames: deploymentGroups.deploymentGroups,
|
deploymentGroupNames: deploymentGroups.deploymentGroups
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
deploymentGroupsInfo.push(...(batchResponse.deploymentGroupsInfo || []));
|
deploymentGroupsInfo.push(...(batchResponse.deploymentGroupsInfo || []))
|
||||||
}
|
}
|
||||||
|
|
||||||
return deploymentGroupsInfo;
|
return deploymentGroupsInfo
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'CodeDeployAutoRollbackMonitorEnabled',
|
name: 'CodeDeployAutoRollbackMonitorEnabled',
|
||||||
description: 'Ensures that auto-rollback and alarm monitoring are enabled for CodeDeploy deployment groups.',
|
description: 'Ensures that auto-rollback and alarm monitoring are enabled for CodeDeploy deployment groups.',
|
||||||
priority: 2,
|
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',
|
awsService: 'CodeDeploy',
|
||||||
awsServiceCategory: 'Deployment',
|
awsServiceCategory: 'Deployment',
|
||||||
bestPracticeCategory: 'Resilience',
|
bestPracticeCategory: 'Resilience',
|
||||||
@ -48,101 +49,96 @@ export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListApplicationsCommand',
|
name: 'ListApplicationsCommand',
|
||||||
reason: 'Retrieve all CodeDeploy applications to analyze their deployment groups.',
|
reason: 'Retrieve all CodeDeploy applications to analyze their deployment groups.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ListDeploymentGroupsCommand',
|
name: 'ListDeploymentGroupsCommand',
|
||||||
reason: 'Fetch deployment groups for each application.',
|
reason: 'Fetch deployment groups for each application.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'BatchGetDeploymentGroupsCommand',
|
name: 'BatchGetDeploymentGroupsCommand',
|
||||||
reason: 'Get detailed information about deployment groups to check configurations.',
|
reason: 'Get detailed information about deployment groups to check configurations.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateDeploymentGroupCommand',
|
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:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const deploymentGroups = await this.getDeploymentGroups();
|
const deploymentGroups = await this.getDeploymentGroups()
|
||||||
|
|
||||||
for (const deploymentGroup of deploymentGroups) {
|
for (const deploymentGroup of deploymentGroups) {
|
||||||
if (
|
if (deploymentGroup.alarmConfiguration?.enabled && deploymentGroup.autoRollbackConfiguration?.enabled) {
|
||||||
deploymentGroup.alarmConfiguration?.enabled &&
|
compliantResources.push(deploymentGroup.deploymentGroupId!)
|
||||||
deploymentGroup.autoRollbackConfiguration?.enabled
|
|
||||||
) {
|
|
||||||
compliantResources.push(deploymentGroup.deploymentGroupId!);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(deploymentGroup.deploymentGroupId!);
|
nonCompliantResources.push(deploymentGroup.deploymentGroupId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
const deploymentGroups = await this.getDeploymentGroups();
|
const deploymentGroups = await this.getDeploymentGroups()
|
||||||
|
|
||||||
for (const groupId of nonCompliantResources) {
|
for (const groupId of nonCompliantResources) {
|
||||||
const deploymentGroupToFix = deploymentGroups.find(
|
const deploymentGroupToFix = deploymentGroups.find((group) => group.deploymentGroupId === groupId)
|
||||||
(group) => group.deploymentGroupId === groupId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!deploymentGroupToFix) {
|
if (!deploymentGroupToFix) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
@ -151,14 +147,14 @@ export class CodeDeployAutoRollbackMonitorEnabled implements BPSet {
|
|||||||
currentDeploymentGroupName: deploymentGroupToFix.deploymentGroupName!,
|
currentDeploymentGroupName: deploymentGroupToFix.deploymentGroupName!,
|
||||||
alarmConfiguration: {
|
alarmConfiguration: {
|
||||||
...deploymentGroupToFix.alarmConfiguration,
|
...deploymentGroupToFix.alarmConfiguration,
|
||||||
enabled: true,
|
enabled: true
|
||||||
},
|
},
|
||||||
autoRollbackConfiguration: {
|
autoRollbackConfiguration: {
|
||||||
...deploymentGroupToFix.autoRollbackConfiguration,
|
...deploymentGroupToFix.autoRollbackConfiguration,
|
||||||
enabled: true,
|
enabled: true
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,34 @@
|
|||||||
import {
|
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand } from '@aws-sdk/client-dynamodb'
|
||||||
DynamoDBClient,
|
|
||||||
ListTablesCommand,
|
|
||||||
DescribeTableCommand,
|
|
||||||
} from '@aws-sdk/client-dynamodb';
|
|
||||||
import {
|
import {
|
||||||
ApplicationAutoScalingClient,
|
ApplicationAutoScalingClient,
|
||||||
RegisterScalableTargetCommand,
|
RegisterScalableTargetCommand,
|
||||||
PutScalingPolicyCommand,
|
PutScalingPolicyCommand,
|
||||||
DescribeScalingPoliciesCommand,
|
DescribeScalingPoliciesCommand
|
||||||
} from '@aws-sdk/client-application-auto-scaling';
|
} from '@aws-sdk/client-application-auto-scaling'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class DynamoDBAutoscalingEnabled implements BPSet {
|
export class DynamoDBAutoscalingEnabled implements BPSet {
|
||||||
private readonly client = new DynamoDBClient({});
|
private readonly client = new DynamoDBClient({})
|
||||||
private readonly autoScalingClient = new ApplicationAutoScalingClient({});
|
private readonly autoScalingClient = new ApplicationAutoScalingClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTables = async () => {
|
private readonly getTables = async () => {
|
||||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}));
|
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||||
const tables = [];
|
const tables = []
|
||||||
for (const tableName of tableNames.TableNames || []) {
|
for (const tableName of tableNames.TableNames || []) {
|
||||||
const tableDetails = await this.memoClient.send(
|
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
|
||||||
new DescribeTableCommand({ TableName: tableName })
|
tables.push(tableDetails.Table!)
|
||||||
);
|
|
||||||
tables.push(tableDetails.Table!);
|
|
||||||
}
|
}
|
||||||
return tables;
|
return tables
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'DynamoDBAutoscalingEnabled',
|
name: 'DynamoDBAutoscalingEnabled',
|
||||||
description: 'Ensures DynamoDB tables have autoscaling enabled for both read and write capacity.',
|
description: 'Ensures DynamoDB tables have autoscaling enabled for both read and write capacity.',
|
||||||
priority: 2,
|
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',
|
awsService: 'DynamoDB',
|
||||||
awsServiceCategory: 'Database',
|
awsServiceCategory: 'Database',
|
||||||
bestPracticeCategory: 'Scalability',
|
bestPracticeCategory: 'Scalability',
|
||||||
@ -42,113 +37,111 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTablesCommand',
|
name: 'ListTablesCommand',
|
||||||
reason: 'List all DynamoDB tables to check their configurations.',
|
reason: 'List all DynamoDB tables to check their configurations.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeTableCommand',
|
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',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RegisterScalableTargetCommand',
|
name: 'RegisterScalableTargetCommand',
|
||||||
reason: 'Register read and write capacity units for autoscaling.',
|
reason: 'Register read and write capacity units for autoscaling.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'PutScalingPolicyCommand',
|
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:
|
adviseBeforeFixFunction:
|
||||||
'Ensure the table’s read and write workloads are predictable to configure appropriate scaling limits.',
|
'Ensure the table’s read and write workloads are predictable to configure appropriate scaling limits.'
|
||||||
});
|
})
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const tables = await this.getTables();
|
const tables = await this.getTables()
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
if (table.BillingModeSummary?.BillingMode === 'PAY_PER_REQUEST') {
|
if (table.BillingModeSummary?.BillingMode === 'PAY_PER_REQUEST') {
|
||||||
compliantResources.push(table.TableArn!);
|
compliantResources.push(table.TableArn!)
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const scalingPolicies = await this.autoScalingClient.send(
|
const scalingPolicies = await this.autoScalingClient.send(
|
||||||
new DescribeScalingPoliciesCommand({
|
new DescribeScalingPoliciesCommand({
|
||||||
ServiceNamespace: 'dynamodb',
|
ServiceNamespace: 'dynamodb',
|
||||||
ResourceId: `table/${table.TableName}`,
|
ResourceId: `table/${table.TableName}`
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
const scalingPolicyDimensions = scalingPolicies.ScalingPolicies?.map(
|
const scalingPolicyDimensions = scalingPolicies.ScalingPolicies?.map((policy) => policy.ScalableDimension)
|
||||||
(policy) => policy.ScalableDimension
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
scalingPolicyDimensions?.includes('dynamodb:table:ReadCapacityUnits') &&
|
scalingPolicyDimensions?.includes('dynamodb:table:ReadCapacityUnits') &&
|
||||||
scalingPolicyDimensions?.includes('dynamodb:table:WriteCapacityUnits')
|
scalingPolicyDimensions?.includes('dynamodb:table:WriteCapacityUnits')
|
||||||
) {
|
) {
|
||||||
compliantResources.push(table.TableArn!);
|
compliantResources.push(table.TableArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(table.TableArn!);
|
nonCompliantResources.push(table.TableArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const tableName = arn.split('/').pop()!;
|
const tableName = arn.split('/').pop()!
|
||||||
|
|
||||||
// Register scalable targets for read and write capacity
|
// Register scalable targets for read and write capacity
|
||||||
await this.autoScalingClient.send(
|
await this.autoScalingClient.send(
|
||||||
@ -157,9 +150,9 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
|
|||||||
ResourceId: `table/${tableName}`,
|
ResourceId: `table/${tableName}`,
|
||||||
ScalableDimension: 'dynamodb:table:ReadCapacityUnits',
|
ScalableDimension: 'dynamodb:table:ReadCapacityUnits',
|
||||||
MinCapacity: 1,
|
MinCapacity: 1,
|
||||||
MaxCapacity: 100,
|
MaxCapacity: 100
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
await this.autoScalingClient.send(
|
await this.autoScalingClient.send(
|
||||||
new RegisterScalableTargetCommand({
|
new RegisterScalableTargetCommand({
|
||||||
@ -167,9 +160,9 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
|
|||||||
ResourceId: `table/${tableName}`,
|
ResourceId: `table/${tableName}`,
|
||||||
ScalableDimension: 'dynamodb:table:WriteCapacityUnits',
|
ScalableDimension: 'dynamodb:table:WriteCapacityUnits',
|
||||||
MinCapacity: 1,
|
MinCapacity: 1,
|
||||||
MaxCapacity: 100,
|
MaxCapacity: 100
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Put scaling policies for read and write capacity
|
// Put scaling policies for read and write capacity
|
||||||
await this.autoScalingClient.send(
|
await this.autoScalingClient.send(
|
||||||
@ -184,11 +177,11 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
|
|||||||
ScaleInCooldown: 60,
|
ScaleInCooldown: 60,
|
||||||
ScaleOutCooldown: 60,
|
ScaleOutCooldown: 60,
|
||||||
PredefinedMetricSpecification: {
|
PredefinedMetricSpecification: {
|
||||||
PredefinedMetricType: 'DynamoDBReadCapacityUtilization',
|
PredefinedMetricType: 'DynamoDBReadCapacityUtilization'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
await this.autoScalingClient.send(
|
await this.autoScalingClient.send(
|
||||||
new PutScalingPolicyCommand({
|
new PutScalingPolicyCommand({
|
||||||
@ -202,11 +195,11 @@ export class DynamoDBAutoscalingEnabled implements BPSet {
|
|||||||
ScaleInCooldown: 60,
|
ScaleInCooldown: 60,
|
||||||
ScaleOutCooldown: 60,
|
ScaleOutCooldown: 60,
|
||||||
PredefinedMetricSpecification: {
|
PredefinedMetricSpecification: {
|
||||||
PredefinedMetricType: 'DynamoDBWriteCapacityUtilization',
|
PredefinedMetricType: 'DynamoDBWriteCapacityUtilization'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,34 @@
|
|||||||
import {
|
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand } from '@aws-sdk/client-dynamodb'
|
||||||
DynamoDBClient,
|
import { BackupClient, ListRecoveryPointsByResourceCommand, StartBackupJobCommand } from '@aws-sdk/client-backup'
|
||||||
ListTablesCommand,
|
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'
|
||||||
DescribeTableCommand,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
} from '@aws-sdk/client-dynamodb';
|
import { Memorizer } from '../../Memorizer'
|
||||||
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 {
|
export class DynamoDBLastBackupRecoveryPointCreated implements BPSet {
|
||||||
private readonly client = new DynamoDBClient({});
|
private readonly client = new DynamoDBClient({})
|
||||||
private readonly backupClient = new BackupClient({});
|
private readonly backupClient = new BackupClient({})
|
||||||
private readonly stsClient = new STSClient({});
|
private readonly stsClient = new STSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private accountId: string | undefined;
|
private accountId: string | undefined
|
||||||
|
|
||||||
private readonly fetchAccountId = async () => {
|
private readonly fetchAccountId = async () => {
|
||||||
if (!this.accountId) {
|
if (!this.accountId) {
|
||||||
const identity = await this.stsClient.send(new GetCallerIdentityCommand({}));
|
const identity = await this.stsClient.send(new GetCallerIdentityCommand({}))
|
||||||
this.accountId = identity.Account!;
|
this.accountId = identity.Account!
|
||||||
}
|
}
|
||||||
return this.accountId;
|
return this.accountId
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getTables = async () => {
|
private readonly getTables = async () => {
|
||||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}));
|
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||||
const tables = [];
|
const tables = []
|
||||||
for (const tableName of tableNames.TableNames || []) {
|
for (const tableName of tableNames.TableNames || []) {
|
||||||
const tableDetails = await this.memoClient.send(
|
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
|
||||||
new DescribeTableCommand({ TableName: tableName })
|
tables.push(tableDetails.Table!)
|
||||||
);
|
|
||||||
tables.push(tableDetails.Table!);
|
|
||||||
}
|
}
|
||||||
return tables;
|
return tables
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'DynamoDBLastBackupRecoveryPointCreated',
|
name: 'DynamoDBLastBackupRecoveryPointCreated',
|
||||||
@ -53,115 +43,114 @@ export class DynamoDBLastBackupRecoveryPointCreated implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTablesCommand',
|
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',
|
name: 'DescribeTableCommand',
|
||||||
reason: 'Fetch details of each DynamoDB table.',
|
reason: 'Fetch details of each DynamoDB table.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ListRecoveryPointsByResourceCommand',
|
name: 'ListRecoveryPointsByResourceCommand',
|
||||||
reason: 'Check recovery points for DynamoDB tables in AWS Backup.',
|
reason: 'Check recovery points for DynamoDB tables in AWS Backup.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'StartBackupJobCommand',
|
name: 'StartBackupJobCommand',
|
||||||
reason: 'Initiate a backup job for non-compliant DynamoDB tables.',
|
reason: 'Initiate a backup job for non-compliant DynamoDB tables.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure the backup vault and IAM role are properly configured for your backup strategy.'
|
||||||
'Ensure the backup vault and IAM role are properly configured for your backup strategy.',
|
})
|
||||||
});
|
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const tables = await this.getTables();
|
const tables = await this.getTables()
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
const recoveryPointsResponse = await this.backupClient.send(
|
const recoveryPointsResponse = await this.backupClient.send(
|
||||||
new ListRecoveryPointsByResourceCommand({
|
new ListRecoveryPointsByResourceCommand({
|
||||||
ResourceArn: table.TableArn,
|
ResourceArn: table.TableArn
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
const recoveryPoints = recoveryPointsResponse.RecoveryPoints || [];
|
const recoveryPoints = recoveryPointsResponse.RecoveryPoints || []
|
||||||
|
|
||||||
if (recoveryPoints.length === 0) {
|
if (recoveryPoints.length === 0) {
|
||||||
nonCompliantResources.push(table.TableArn!);
|
nonCompliantResources.push(table.TableArn!)
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const latestRecoveryPoint = recoveryPoints
|
const latestRecoveryPoint = recoveryPoints
|
||||||
.map((point) => new Date(point.CreationDate!))
|
.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) {
|
if (new Date().getTime() - latestRecoveryPoint.getTime() > 86400000) {
|
||||||
nonCompliantResources.push(table.TableArn!);
|
nonCompliantResources.push(table.TableArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(table.TableArn!);
|
compliantResources.push(table.TableArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
const accountId = await this.fetchAccountId();
|
const accountId = await this.fetchAccountId()
|
||||||
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
await this.backupClient.send(
|
await this.backupClient.send(
|
||||||
new StartBackupJobCommand({
|
new StartBackupJobCommand({
|
||||||
ResourceArn: arn,
|
ResourceArn: arn,
|
||||||
BackupVaultName: 'Default',
|
BackupVaultName: 'Default',
|
||||||
IamRoleArn: `arn:aws:iam::${accountId}:role/service-role/BackupDefaultServiceRole`,
|
IamRoleArn: `arn:aws:iam::${accountId}:role/service-role/BackupDefaultServiceRole`
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,32 +3,31 @@ import {
|
|||||||
ListTablesCommand,
|
ListTablesCommand,
|
||||||
DescribeTableCommand,
|
DescribeTableCommand,
|
||||||
DescribeContinuousBackupsCommand,
|
DescribeContinuousBackupsCommand,
|
||||||
UpdateContinuousBackupsCommand,
|
UpdateContinuousBackupsCommand
|
||||||
} from '@aws-sdk/client-dynamodb';
|
} from '@aws-sdk/client-dynamodb'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class DynamoDBPITREnabled implements BPSet {
|
export class DynamoDBPITREnabled implements BPSet {
|
||||||
private readonly client = new DynamoDBClient({});
|
private readonly client = new DynamoDBClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTables = async () => {
|
private readonly getTables = async () => {
|
||||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}));
|
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||||
const tables = [];
|
const tables = []
|
||||||
for (const tableName of tableNames.TableNames || []) {
|
for (const tableName of tableNames.TableNames || []) {
|
||||||
const tableDetails = await this.memoClient.send(
|
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
|
||||||
new DescribeTableCommand({ TableName: tableName })
|
tables.push(tableDetails.Table!)
|
||||||
);
|
|
||||||
tables.push(tableDetails.Table!);
|
|
||||||
}
|
}
|
||||||
return tables;
|
return tables
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'DynamoDBPITREnabled',
|
name: 'DynamoDBPITREnabled',
|
||||||
description: 'Ensures that Point-In-Time Recovery (PITR) is enabled for DynamoDB tables.',
|
description: 'Ensures that Point-In-Time Recovery (PITR) is enabled for DynamoDB tables.',
|
||||||
priority: 1,
|
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',
|
awsService: 'DynamoDB',
|
||||||
awsServiceCategory: 'Database',
|
awsServiceCategory: 'Database',
|
||||||
bestPracticeCategory: 'Backup and Recovery',
|
bestPracticeCategory: 'Backup and Recovery',
|
||||||
@ -37,108 +36,109 @@ export class DynamoDBPITREnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTablesCommand',
|
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',
|
name: 'DescribeTableCommand',
|
||||||
reason: 'Fetch details of each DynamoDB table.',
|
reason: 'Fetch details of each DynamoDB table.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeContinuousBackupsCommand',
|
name: 'DescribeContinuousBackupsCommand',
|
||||||
reason: 'Check if PITR is enabled for each table.',
|
reason: 'Check if PITR is enabled for each table.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateContinuousBackupsCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const tables = await this.getTables();
|
const tables = await this.getTables()
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
const backupStatus = await this.memoClient.send(
|
const backupStatus = await this.memoClient.send(
|
||||||
new DescribeContinuousBackupsCommand({
|
new DescribeContinuousBackupsCommand({
|
||||||
TableName: table.TableName!,
|
TableName: table.TableName!
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
backupStatus.ContinuousBackupsDescription?.PointInTimeRecoveryDescription
|
backupStatus.ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus ===
|
||||||
?.PointInTimeRecoveryStatus === 'ENABLED'
|
'ENABLED'
|
||||||
) {
|
) {
|
||||||
compliantResources.push(table.TableArn!);
|
compliantResources.push(table.TableArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(table.TableArn!);
|
nonCompliantResources.push(table.TableArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const tableName = arn.split('/').pop()!;
|
const tableName = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateContinuousBackupsCommand({
|
new UpdateContinuousBackupsCommand({
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
PointInTimeRecoverySpecification: {
|
PointInTimeRecoverySpecification: {
|
||||||
PointInTimeRecoveryEnabled: true,
|
PointInTimeRecoveryEnabled: true
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,20 @@
|
|||||||
import {
|
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, UpdateTableCommand } from '@aws-sdk/client-dynamodb'
|
||||||
DynamoDBClient,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
ListTablesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
DescribeTableCommand,
|
|
||||||
UpdateTableCommand,
|
|
||||||
} from '@aws-sdk/client-dynamodb';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class DynamoDBTableDeletionProtectionEnabled implements BPSet {
|
export class DynamoDBTableDeletionProtectionEnabled implements BPSet {
|
||||||
private readonly client = new DynamoDBClient({});
|
private readonly client = new DynamoDBClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTables = async () => {
|
private readonly getTables = async () => {
|
||||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}));
|
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||||
const tables = [];
|
const tables = []
|
||||||
for (const tableName of tableNames.TableNames || []) {
|
for (const tableName of tableNames.TableNames || []) {
|
||||||
const tableDetails = await this.memoClient.send(
|
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
|
||||||
new DescribeTableCommand({ TableName: tableName })
|
tables.push(tableDetails.Table!)
|
||||||
);
|
|
||||||
tables.push(tableDetails.Table!);
|
|
||||||
}
|
}
|
||||||
return tables;
|
return tables
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'DynamoDBTableDeletionProtectionEnabled',
|
name: 'DynamoDBTableDeletionProtectionEnabled',
|
||||||
@ -36,93 +29,93 @@ export class DynamoDBTableDeletionProtectionEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTablesCommand',
|
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',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateTableCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const tables = await this.getTables();
|
const tables = await this.getTables()
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
if (table.DeletionProtectionEnabled) {
|
if (table.DeletionProtectionEnabled) {
|
||||||
compliantResources.push(table.TableArn!);
|
compliantResources.push(table.TableArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(table.TableArn!);
|
nonCompliantResources.push(table.TableArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const tableName = arn.split('/').pop()!;
|
const tableName = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateTableCommand({
|
new UpdateTableCommand({
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
DeletionProtectionEnabled: true,
|
DeletionProtectionEnabled: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,20 @@
|
|||||||
import {
|
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, UpdateTableCommand } from '@aws-sdk/client-dynamodb'
|
||||||
DynamoDBClient,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
ListTablesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
DescribeTableCommand,
|
|
||||||
UpdateTableCommand,
|
|
||||||
} from '@aws-sdk/client-dynamodb';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class DynamoDBTableEncryptedKMS implements BPSet {
|
export class DynamoDBTableEncryptedKMS implements BPSet {
|
||||||
private readonly client = new DynamoDBClient({});
|
private readonly client = new DynamoDBClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTables = async () => {
|
private readonly getTables = async () => {
|
||||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}));
|
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||||
const tables = [];
|
const tables = []
|
||||||
for (const tableName of tableNames.TableNames || []) {
|
for (const tableName of tableNames.TableNames || []) {
|
||||||
const tableDetails = await this.memoClient.send(
|
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
|
||||||
new DescribeTableCommand({ TableName: tableName })
|
tables.push(tableDetails.Table!)
|
||||||
);
|
|
||||||
tables.push(tableDetails.Table!);
|
|
||||||
}
|
}
|
||||||
return tables;
|
return tables
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'DynamoDBTableEncryptedKMS',
|
name: 'DynamoDBTableEncryptedKMS',
|
||||||
@ -36,102 +29,99 @@ export class DynamoDBTableEncryptedKMS implements BPSet {
|
|||||||
name: 'kms-key-id',
|
name: 'kms-key-id',
|
||||||
description: 'The ID of the KMS key used to encrypt the DynamoDB table.',
|
description: 'The ID of the KMS key used to encrypt the DynamoDB table.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTablesCommand',
|
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',
|
name: 'DescribeTableCommand',
|
||||||
reason: 'Fetch details of each DynamoDB table, including SSE configuration.',
|
reason: 'Fetch details of each DynamoDB table, including SSE configuration.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateTableCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const tables = await this.getTables();
|
const tables = await this.getTables()
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
if (
|
if (table.SSEDescription?.Status === 'ENABLED' && table.SSEDescription?.SSEType === 'KMS') {
|
||||||
table.SSEDescription?.Status === 'ENABLED' &&
|
compliantResources.push(table.TableArn!)
|
||||||
table.SSEDescription?.SSEType === 'KMS'
|
|
||||||
) {
|
|
||||||
compliantResources.push(table.TableArn!);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(table.TableArn!);
|
nonCompliantResources.push(table.TableArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
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) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const tableName = arn.split('/').pop()!;
|
const tableName = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateTableCommand({
|
new UpdateTableCommand({
|
||||||
@ -139,10 +129,10 @@ export class DynamoDBTableEncryptedKMS implements BPSet {
|
|||||||
SSESpecification: {
|
SSESpecification: {
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
SSEType: 'KMS',
|
SSEType: 'KMS',
|
||||||
KMSMasterKeyId: kmsKeyId,
|
KMSMasterKeyId: kmsKeyId
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,27 @@
|
|||||||
import {
|
import { DynamoDBClient, ListTablesCommand, DescribeTableCommand, UpdateTableCommand } from '@aws-sdk/client-dynamodb'
|
||||||
DynamoDBClient,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
ListTablesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
DescribeTableCommand,
|
|
||||||
UpdateTableCommand,
|
|
||||||
} from '@aws-sdk/client-dynamodb';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class DynamoDBTableEncryptionEnabled implements BPSet {
|
export class DynamoDBTableEncryptionEnabled implements BPSet {
|
||||||
private readonly client = new DynamoDBClient({});
|
private readonly client = new DynamoDBClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTables = async () => {
|
private readonly getTables = async () => {
|
||||||
const tableNames = await this.memoClient.send(new ListTablesCommand({}));
|
const tableNames = await this.memoClient.send(new ListTablesCommand({}))
|
||||||
const tables = [];
|
const tables = []
|
||||||
for (const tableName of tableNames.TableNames || []) {
|
for (const tableName of tableNames.TableNames || []) {
|
||||||
const tableDetails = await this.memoClient.send(
|
const tableDetails = await this.memoClient.send(new DescribeTableCommand({ TableName: tableName }))
|
||||||
new DescribeTableCommand({ TableName: tableName })
|
tables.push(tableDetails.Table!)
|
||||||
);
|
|
||||||
tables.push(tableDetails.Table!);
|
|
||||||
}
|
}
|
||||||
return tables;
|
return tables
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'DynamoDBTableEncryptionEnabled',
|
name: 'DynamoDBTableEncryptionEnabled',
|
||||||
description: 'Ensures that DynamoDB tables have server-side encryption enabled.',
|
description: 'Ensures that DynamoDB tables have server-side encryption enabled.',
|
||||||
priority: 3,
|
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',
|
awsService: 'DynamoDB',
|
||||||
awsServiceCategory: 'Database',
|
awsServiceCategory: 'Database',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -36,95 +30,95 @@ export class DynamoDBTableEncryptionEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTablesCommand',
|
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',
|
name: 'DescribeTableCommand',
|
||||||
reason: 'Fetch details of each DynamoDB table, including encryption settings.',
|
reason: 'Fetch details of each DynamoDB table, including encryption settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateTableCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const tables = await this.getTables();
|
const tables = await this.getTables()
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
if (table.SSEDescription?.Status === 'ENABLED') {
|
if (table.SSEDescription?.Status === 'ENABLED') {
|
||||||
compliantResources.push(table.TableArn!);
|
compliantResources.push(table.TableArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(table.TableArn!);
|
nonCompliantResources.push(table.TableArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const tableName = arn.split('/').pop()!;
|
const tableName = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateTableCommand({
|
new UpdateTableCommand({
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
SSESpecification: {
|
SSESpecification: {
|
||||||
Enabled: true,
|
Enabled: true
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { EC2Client, GetEbsEncryptionByDefaultCommand, EnableEbsEncryptionByDefaultCommand } from '@aws-sdk/client-ec2'
|
||||||
EC2Client,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeVolumesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetEbsEncryptionByDefaultCommand,
|
|
||||||
EnableEbsEncryptionByDefaultCommand,
|
|
||||||
} from '@aws-sdk/client-ec2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2EbsEncryptionByDefault implements BPSet {
|
export class EC2EbsEncryptionByDefault implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2EbsEncryptionByDefault',
|
name: 'EC2EbsEncryptionByDefault',
|
||||||
description: 'Ensures that EBS encryption is enabled by default for all volumes in the AWS account.',
|
description: 'Ensures that EBS encryption is enabled by default for all volumes in the AWS account.',
|
||||||
priority: 1,
|
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',
|
awsService: 'EC2',
|
||||||
awsServiceCategory: 'Compute',
|
awsServiceCategory: 'Compute',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -24,79 +20,79 @@ export class EC2EbsEncryptionByDefault implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetEbsEncryptionByDefaultCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'EnableEbsEncryptionByDefaultCommand',
|
name: 'EnableEbsEncryptionByDefaultCommand',
|
||||||
reason: 'Enable EBS encryption by default for the account.',
|
reason: 'Enable EBS encryption by default for the account.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction:
|
||||||
'Ensure enabling EBS encryption by default aligns with your organization’s security policies.',
|
'Ensure enabling EBS encryption by default aligns with your organization’s security policies.'
|
||||||
});
|
})
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
|
|
||||||
const response = await this.client.send(new GetEbsEncryptionByDefaultCommand({}));
|
const response = await this.client.send(new GetEbsEncryptionByDefaultCommand({}))
|
||||||
if (response.EbsEncryptionByDefault) {
|
if (response.EbsEncryptionByDefault) {
|
||||||
compliantResources.push('EBS Encryption By Default');
|
compliantResources.push('EBS Encryption By Default')
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push('EBS Encryption By Default');
|
nonCompliantResources.push('EBS Encryption By Default')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async () => {
|
public readonly fixImpl: BPSetFixFn = async () => {
|
||||||
await this.client.send(new EnableEbsEncryptionByDefaultCommand({}));
|
await this.client.send(new EnableEbsEncryptionByDefaultCommand({}))
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import {
|
import { DescribeInstancesCommand, EC2Client, ModifyInstanceMetadataOptionsCommand } from '@aws-sdk/client-ec2'
|
||||||
DescribeInstancesCommand,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
EC2Client,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyInstanceMetadataOptionsCommand,
|
|
||||||
} from '@aws-sdk/client-ec2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2Imdsv2Check implements BPSet {
|
export class EC2Imdsv2Check implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2Imdsv2Check',
|
name: 'EC2Imdsv2Check',
|
||||||
@ -23,92 +19,91 @@ export class EC2Imdsv2Check implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeInstancesCommand',
|
name: 'DescribeInstancesCommand',
|
||||||
reason: 'Retrieve all EC2 instances and check their metadata options.',
|
reason: 'Retrieve all EC2 instances and check their metadata options.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyInstanceMetadataOptionsCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||||
|
|
||||||
for (const reservation of response.Reservations || []) {
|
for (const reservation of response.Reservations || []) {
|
||||||
for (const instance of reservation.Instances || []) {
|
for (const instance of reservation.Instances || []) {
|
||||||
if (instance.State?.Name === 'terminated')
|
if (instance.State?.Name === 'terminated') continue
|
||||||
continue
|
|
||||||
|
|
||||||
if (instance.MetadataOptions?.HttpTokens === 'required') {
|
if (instance.MetadataOptions?.HttpTokens === 'required') {
|
||||||
compliantResources.push(instance.InstanceId!);
|
compliantResources.push(instance.InstanceId!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(instance.InstanceId!);
|
nonCompliantResources.push(instance.InstanceId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const instanceId of nonCompliantResources) {
|
for (const instanceId of nonCompliantResources) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyInstanceMetadataOptionsCommand({
|
new ModifyInstanceMetadataOptionsCommand({
|
||||||
InstanceId: instanceId,
|
InstanceId: instanceId,
|
||||||
HttpTokens: 'required',
|
HttpTokens: 'required'
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import {
|
import { DescribeInstancesCommand, EC2Client, MonitorInstancesCommand } from '@aws-sdk/client-ec2'
|
||||||
DescribeInstancesCommand,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
EC2Client,
|
import { Memorizer } from '../../Memorizer'
|
||||||
MonitorInstancesCommand,
|
|
||||||
} from '@aws-sdk/client-ec2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2InstanceDetailedMonitoringEnabled implements BPSet {
|
export class EC2InstanceDetailedMonitoringEnabled implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2InstanceDetailedMonitoringEnabled',
|
name: 'EC2InstanceDetailedMonitoringEnabled',
|
||||||
@ -23,92 +19,91 @@ export class EC2InstanceDetailedMonitoringEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeInstancesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'MonitorInstancesCommand',
|
name: 'MonitorInstancesCommand',
|
||||||
reason: 'Enable detailed monitoring for non-compliant EC2 instances.',
|
reason: 'Enable detailed monitoring for non-compliant EC2 instances.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||||
|
|
||||||
for (const reservation of response.Reservations || []) {
|
for (const reservation of response.Reservations || []) {
|
||||||
for (const instance of reservation.Instances || []) {
|
for (const instance of reservation.Instances || []) {
|
||||||
if (instance.State?.Name === 'terminated')
|
if (instance.State?.Name === 'terminated') continue
|
||||||
continue
|
|
||||||
|
|
||||||
if (instance.Monitoring?.State === 'enabled') {
|
if (instance.Monitoring?.State === 'enabled') {
|
||||||
compliantResources.push(instance.InstanceId!);
|
compliantResources.push(instance.InstanceId!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(instance.InstanceId!);
|
nonCompliantResources.push(instance.InstanceId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
if (nonCompliantResources.length > 0) {
|
if (nonCompliantResources.length > 0) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new MonitorInstancesCommand({
|
new MonitorInstancesCommand({
|
||||||
InstanceIds: nonCompliantResources,
|
InstanceIds: nonCompliantResources
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,19 @@
|
|||||||
import {
|
import { EC2Client, DescribeInstancesCommand } from '@aws-sdk/client-ec2'
|
||||||
EC2Client,
|
import { SSMClient, DescribeInstanceInformationCommand } from '@aws-sdk/client-ssm'
|
||||||
DescribeInstancesCommand,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
} from '@aws-sdk/client-ec2';
|
import { Memorizer } from '../../Memorizer'
|
||||||
import {
|
|
||||||
SSMClient,
|
|
||||||
DescribeInstanceInformationCommand,
|
|
||||||
} from '@aws-sdk/client-ssm';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2InstanceManagedBySystemsManager implements BPSet {
|
export class EC2InstanceManagedBySystemsManager implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly ssmClient = new SSMClient({});
|
private readonly ssmClient = new SSMClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2InstanceManagedBySystemsManager',
|
name: 'EC2InstanceManagedBySystemsManager',
|
||||||
description: 'Ensures that EC2 instances are managed by AWS Systems Manager.',
|
description: 'Ensures that EC2 instances are managed by AWS Systems Manager.',
|
||||||
priority: 2,
|
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',
|
awsService: 'EC2',
|
||||||
awsServiceCategory: 'Compute',
|
awsServiceCategory: 'Compute',
|
||||||
bestPracticeCategory: 'Management and Governance',
|
bestPracticeCategory: 'Management and Governance',
|
||||||
@ -28,80 +23,75 @@ export class EC2InstanceManagedBySystemsManager implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeInstancesCommand',
|
name: 'DescribeInstancesCommand',
|
||||||
reason: 'Retrieve the list of all EC2 instances.',
|
reason: 'Retrieve the list of all EC2 instances.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeInstanceInformationCommand',
|
name: 'DescribeInstanceInformationCommand',
|
||||||
reason: 'Retrieve information about instances managed by Systems Manager.',
|
reason: 'Retrieve information about instances managed by Systems Manager.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||||
const ssmResponse = await this.ssmClient.send(
|
const ssmResponse = await this.ssmClient.send(new DescribeInstanceInformationCommand({}))
|
||||||
new DescribeInstanceInformationCommand({})
|
|
||||||
);
|
|
||||||
|
|
||||||
const managedInstanceIds = ssmResponse.InstanceInformationList?.map(
|
const managedInstanceIds = ssmResponse.InstanceInformationList?.map((info) => info.InstanceId)
|
||||||
(info) => info.InstanceId
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const reservation of response.Reservations || []) {
|
for (const reservation of response.Reservations || []) {
|
||||||
for (const instance of reservation.Instances || []) {
|
for (const instance of reservation.Instances || []) {
|
||||||
if (instance.State?.Name === 'terminated')
|
if (instance.State?.Name === 'terminated') continue
|
||||||
continue
|
|
||||||
|
|
||||||
if (managedInstanceIds?.includes(instance.InstanceId!)) {
|
if (managedInstanceIds?.includes(instance.InstanceId!)) {
|
||||||
compliantResources.push(instance.InstanceId!);
|
compliantResources.push(instance.InstanceId!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(instance.InstanceId!);
|
nonCompliantResources.push(instance.InstanceId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async () => {
|
public readonly fix: BPSetFixFn = async () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Fix logic for EC2InstanceManagedBySystemsManager is not directly applicable. Systems Manager Agent setup requires manual intervention.'
|
'Fix logic for EC2InstanceManagedBySystemsManager is not directly applicable. Systems Manager Agent setup requires manual intervention.'
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import {
|
import { EC2Client, DescribeInstancesCommand, AssociateIamInstanceProfileCommand } from '@aws-sdk/client-ec2'
|
||||||
EC2Client,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeInstancesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
AssociateIamInstanceProfileCommand,
|
|
||||||
} from '@aws-sdk/client-ec2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2InstanceProfileAttached implements BPSet {
|
export class EC2InstanceProfileAttached implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2InstanceProfileAttached',
|
name: 'EC2InstanceProfileAttached',
|
||||||
@ -23,108 +19,105 @@ export class EC2InstanceProfileAttached implements BPSet {
|
|||||||
name: 'iam-instance-profile',
|
name: 'iam-instance-profile',
|
||||||
description: 'The name of the IAM instance profile to attach.',
|
description: 'The name of the IAM instance profile to attach.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'EC2InstanceProfile',
|
example: 'EC2InstanceProfile'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeInstancesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'AssociateIamInstanceProfileCommand',
|
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:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||||
|
|
||||||
for (const reservation of response.Reservations || []) {
|
for (const reservation of response.Reservations || []) {
|
||||||
for (const instance of reservation.Instances || []) {
|
for (const instance of reservation.Instances || []) {
|
||||||
if (instance.State?.Name === 'terminated')
|
if (instance.State?.Name === 'terminated') continue
|
||||||
continue
|
|
||||||
|
|
||||||
if (instance.IamInstanceProfile) {
|
if (instance.IamInstanceProfile) {
|
||||||
compliantResources.push(instance.InstanceId!);
|
compliantResources.push(instance.InstanceId!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(instance.InstanceId!);
|
nonCompliantResources.push(instance.InstanceId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const iamInstanceProfile = requiredParametersForFix.find(
|
const iamInstanceProfile = requiredParametersForFix.find((param) => param.name === 'iam-instance-profile')?.value
|
||||||
(param) => param.name === 'iam-instance-profile'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!iamInstanceProfile) {
|
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) {
|
for (const instanceId of nonCompliantResources) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new AssociateIamInstanceProfileCommand({
|
new AssociateIamInstanceProfileCommand({
|
||||||
InstanceId: instanceId,
|
InstanceId: instanceId,
|
||||||
IamInstanceProfile: { Name: iamInstanceProfile },
|
IamInstanceProfile: { Name: iamInstanceProfile }
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import {
|
import { EC2Client, DescribeInstancesCommand } from '@aws-sdk/client-ec2'
|
||||||
EC2Client,
|
import { BPSet, BPSetStats } from '../../types'
|
||||||
DescribeInstancesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
} from '@aws-sdk/client-ec2';
|
|
||||||
import { BPSet, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2NoAmazonKeyPair implements BPSet {
|
export class EC2NoAmazonKeyPair implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2NoAmazonKeyPair',
|
name: 'EC2NoAmazonKeyPair',
|
||||||
@ -22,70 +19,69 @@ export class EC2NoAmazonKeyPair implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeInstancesCommand',
|
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: [],
|
commandUsedInFixFunction: [],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||||
|
|
||||||
for (const reservation of response.Reservations || []) {
|
for (const reservation of response.Reservations || []) {
|
||||||
for (const instance of reservation.Instances || []) {
|
for (const instance of reservation.Instances || []) {
|
||||||
if (instance.State?.Name === 'terminated')
|
if (instance.State?.Name === 'terminated') continue
|
||||||
continue
|
|
||||||
|
|
||||||
if (instance.KeyName) {
|
if (instance.KeyName) {
|
||||||
nonCompliantResources.push(instance.InstanceId!);
|
nonCompliantResources.push(instance.InstanceId!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(instance.InstanceId!);
|
compliantResources.push(instance.InstanceId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async () => {
|
public readonly fix = async () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Fix logic for EC2NoAmazonKeyPair is not applicable. Key pairs must be removed manually or during instance creation.'
|
'Fix logic for EC2NoAmazonKeyPair is not applicable. Key pairs must be removed manually or during instance creation.'
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
import {
|
import { EC2Client, DescribeInstancesCommand, TerminateInstancesCommand } from '@aws-sdk/client-ec2'
|
||||||
EC2Client,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeInstancesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
TerminateInstancesCommand,
|
|
||||||
} from '@aws-sdk/client-ec2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2StoppedInstance implements BPSet {
|
export class EC2StoppedInstance implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2StoppedInstance',
|
name: 'EC2StoppedInstance',
|
||||||
description: 'Ensures that stopped EC2 instances are identified and terminated if necessary.',
|
description: 'Ensures that stopped EC2 instances are identified and terminated if necessary.',
|
||||||
priority: 3,
|
priority: 3,
|
||||||
priorityReason:
|
priorityReason: 'Stopped instances can incur costs for storage and IP addresses without being in use.',
|
||||||
'Stopped instances can incur costs for storage and IP addresses without being in use.',
|
|
||||||
awsService: 'EC2',
|
awsService: 'EC2',
|
||||||
awsServiceCategory: 'Compute',
|
awsServiceCategory: 'Compute',
|
||||||
bestPracticeCategory: 'Cost Optimization',
|
bestPracticeCategory: 'Cost Optimization',
|
||||||
@ -24,91 +19,91 @@ export class EC2StoppedInstance implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeInstancesCommand',
|
name: 'DescribeInstancesCommand',
|
||||||
reason: 'Retrieve EC2 instances to check their state.',
|
reason: 'Retrieve EC2 instances to check their state.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'TerminateInstancesCommand',
|
name: 'TerminateInstancesCommand',
|
||||||
reason: 'Terminate stopped EC2 instances to prevent unnecessary costs.',
|
reason: 'Terminate stopped EC2 instances to prevent unnecessary costs.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||||
|
|
||||||
for (const reservation of response.Reservations || []) {
|
for (const reservation of response.Reservations || []) {
|
||||||
for (const instance of reservation.Instances || []) {
|
for (const instance of reservation.Instances || []) {
|
||||||
if (instance.State?.Name === 'stopped') {
|
if (instance.State?.Name === 'stopped') {
|
||||||
nonCompliantResources.push(instance.InstanceId!);
|
nonCompliantResources.push(instance.InstanceId!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(instance.InstanceId!);
|
compliantResources.push(instance.InstanceId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
if (nonCompliantResources.length === 0) {
|
if (nonCompliantResources.length === 0) {
|
||||||
return; // No stopped instances to terminate
|
return // No stopped instances to terminate
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new TerminateInstancesCommand({
|
new TerminateInstancesCommand({
|
||||||
InstanceIds: nonCompliantResources,
|
InstanceIds: nonCompliantResources
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
import {
|
import { EC2Client, DescribeInstancesCommand, ModifyInstanceMetadataOptionsCommand } from '@aws-sdk/client-ec2'
|
||||||
EC2Client,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeInstancesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyInstanceMetadataOptionsCommand,
|
|
||||||
} from '@aws-sdk/client-ec2';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EC2TokenHopLimitCheck implements BPSet {
|
export class EC2TokenHopLimitCheck implements BPSet {
|
||||||
private readonly client = new EC2Client({});
|
private readonly client = new EC2Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EC2TokenHopLimitCheck',
|
name: 'EC2TokenHopLimitCheck',
|
||||||
description: 'Ensures that EC2 instances have a Metadata Options HttpPutResponseHopLimit of 1.',
|
description: 'Ensures that EC2 instances have a Metadata Options HttpPutResponseHopLimit of 1.',
|
||||||
priority: 3,
|
priority: 3,
|
||||||
priorityReason:
|
priorityReason: 'Setting the HttpPutResponseHopLimit to 1 ensures secure access to the instance metadata.',
|
||||||
'Setting the HttpPutResponseHopLimit to 1 ensures secure access to the instance metadata.',
|
|
||||||
awsService: 'EC2',
|
awsService: 'EC2',
|
||||||
awsServiceCategory: 'Compute',
|
awsServiceCategory: 'Compute',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -24,96 +19,91 @@ export class EC2TokenHopLimitCheck implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeInstancesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyInstanceMetadataOptionsCommand',
|
name: 'ModifyInstanceMetadataOptionsCommand',
|
||||||
reason: 'Update the HttpPutResponseHopLimit to enforce secure metadata access.',
|
reason: 'Update the HttpPutResponseHopLimit to enforce secure metadata access.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure modifying instance metadata options aligns with your operational policies.'
|
||||||
'Ensure modifying instance metadata options aligns with your operational policies.',
|
})
|
||||||
});
|
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const response = await this.memoClient.send(new DescribeInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeInstancesCommand({}))
|
||||||
|
|
||||||
for (const reservation of response.Reservations || []) {
|
for (const reservation of response.Reservations || []) {
|
||||||
for (const instance of reservation.Instances || []) {
|
for (const instance of reservation.Instances || []) {
|
||||||
if (instance.State?.Name === 'terminated')
|
if (instance.State?.Name === 'terminated') continue
|
||||||
continue
|
|
||||||
|
|
||||||
if (
|
if (instance.MetadataOptions?.HttpPutResponseHopLimit && instance.MetadataOptions.HttpPutResponseHopLimit < 2) {
|
||||||
instance.MetadataOptions?.HttpPutResponseHopLimit &&
|
compliantResources.push(instance.InstanceId!)
|
||||||
instance.MetadataOptions.HttpPutResponseHopLimit < 2
|
|
||||||
) {
|
|
||||||
compliantResources.push(instance.InstanceId!);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(instance.InstanceId!);
|
nonCompliantResources.push(instance.InstanceId!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const instanceId of nonCompliantResources) {
|
for (const instanceId of nonCompliantResources) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyInstanceMetadataOptionsCommand({
|
new ModifyInstanceMetadataOptionsCommand({
|
||||||
InstanceId: instanceId,
|
InstanceId: instanceId,
|
||||||
HttpPutResponseHopLimit: 1,
|
HttpPutResponseHopLimit: 1
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,19 +5,19 @@ import {
|
|||||||
ListImagesCommand,
|
ListImagesCommand,
|
||||||
BatchGetImageCommand,
|
BatchGetImageCommand,
|
||||||
PutImageCommand,
|
PutImageCommand,
|
||||||
DeleteRepositoryCommand,
|
DeleteRepositoryCommand
|
||||||
} from '@aws-sdk/client-ecr';
|
} from '@aws-sdk/client-ecr'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECRKmsEncryption1 implements BPSet {
|
export class ECRKmsEncryption1 implements BPSet {
|
||||||
private readonly client = new ECRClient({});
|
private readonly client = new ECRClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getRepositories = async () => {
|
private readonly getRepositories = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}));
|
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||||
return response.repositories || [];
|
return response.repositories || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECRKmsEncryption1',
|
name: 'ECRKmsEncryption1',
|
||||||
@ -32,135 +32,131 @@ export class ECRKmsEncryption1 implements BPSet {
|
|||||||
name: 'kms-key-id',
|
name: 'kms-key-id',
|
||||||
description: 'The ID of the KMS key used to encrypt the ECR repository.',
|
description: 'The ID of the KMS key used to encrypt the ECR repository.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: true,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeRepositoriesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'CreateRepositoryCommand',
|
name: 'CreateRepositoryCommand',
|
||||||
reason: 'Create a new repository with KMS encryption.',
|
reason: 'Create a new repository with KMS encryption.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ListImagesCommand',
|
name: 'ListImagesCommand',
|
||||||
reason: 'List all images in the existing repository for migration.',
|
reason: 'List all images in the existing repository for migration.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'BatchGetImageCommand',
|
name: 'BatchGetImageCommand',
|
||||||
reason: 'Retrieve image manifests for migration to the new repository.',
|
reason: 'Retrieve image manifests for migration to the new repository.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'PutImageCommand',
|
name: 'PutImageCommand',
|
||||||
reason: 'Push images to the newly created repository.',
|
reason: 'Push images to the newly created repository.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DeleteRepositoryCommand',
|
name: 'DeleteRepositoryCommand',
|
||||||
reason: 'Delete the old repository after migration.',
|
reason: 'Delete the old repository after migration.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const repositories = await this.getRepositories();
|
const repositories = await this.getRepositories()
|
||||||
|
|
||||||
for (const repository of repositories) {
|
for (const repository of repositories) {
|
||||||
if (repository.encryptionConfiguration?.encryptionType === 'KMS') {
|
if (repository.encryptionConfiguration?.encryptionType === 'KMS') {
|
||||||
compliantResources.push(repository.repositoryArn!);
|
compliantResources.push(repository.repositoryArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(repository.repositoryArn!);
|
nonCompliantResources.push(repository.repositoryArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
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) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const repositoryName = arn.split('/').pop()!;
|
const repositoryName = arn.split('/').pop()!
|
||||||
|
|
||||||
// Create a new repository with KMS encryption
|
// Create a new repository with KMS encryption
|
||||||
const newRepositoryName = `${repositoryName}-kms`;
|
const newRepositoryName = `${repositoryName}-kms`
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new CreateRepositoryCommand({
|
new CreateRepositoryCommand({
|
||||||
repositoryName: newRepositoryName,
|
repositoryName: newRepositoryName,
|
||||||
encryptionConfiguration: {
|
encryptionConfiguration: {
|
||||||
encryptionType: 'KMS',
|
encryptionType: 'KMS',
|
||||||
kmsKey: kmsKeyId,
|
kmsKey: kmsKeyId
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Get all images in the existing repository
|
// Get all images in the existing repository
|
||||||
const listImagesResponse = await this.client.send(
|
const listImagesResponse = await this.client.send(new ListImagesCommand({ repositoryName }))
|
||||||
new ListImagesCommand({ repositoryName })
|
const imageIds = listImagesResponse.imageIds || []
|
||||||
);
|
|
||||||
const imageIds = listImagesResponse.imageIds || [];
|
|
||||||
|
|
||||||
if (imageIds.length > 0) {
|
if (imageIds.length > 0) {
|
||||||
const batchGetImageResponse = await this.client.send(
|
const batchGetImageResponse = await this.client.send(new BatchGetImageCommand({ repositoryName, imageIds }))
|
||||||
new BatchGetImageCommand({ repositoryName, imageIds })
|
|
||||||
);
|
|
||||||
|
|
||||||
// Push images to the new repository
|
// Push images to the new repository
|
||||||
for (const image of batchGetImageResponse.images || []) {
|
for (const image of batchGetImageResponse.images || []) {
|
||||||
@ -168,9 +164,9 @@ export class ECRKmsEncryption1 implements BPSet {
|
|||||||
new PutImageCommand({
|
new PutImageCommand({
|
||||||
repositoryName: newRepositoryName,
|
repositoryName: newRepositoryName,
|
||||||
imageManifest: image.imageManifest,
|
imageManifest: image.imageManifest,
|
||||||
imageTag: image.imageId?.imageTag,
|
imageTag: image.imageId?.imageTag
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,9 +174,9 @@ export class ECRKmsEncryption1 implements BPSet {
|
|||||||
await this.client.send(
|
await this.client.send(
|
||||||
new DeleteRepositoryCommand({
|
new DeleteRepositoryCommand({
|
||||||
repositoryName,
|
repositoryName,
|
||||||
force: true,
|
force: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,21 @@
|
|||||||
import {
|
import { ECRClient, DescribeRepositoriesCommand, PutImageScanningConfigurationCommand } from '@aws-sdk/client-ecr'
|
||||||
ECRClient,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeRepositoriesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
PutImageScanningConfigurationCommand,
|
|
||||||
} from '@aws-sdk/client-ecr';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class ECRPrivateImageScanningEnabled implements BPSet {
|
export class ECRPrivateImageScanningEnabled implements BPSet {
|
||||||
private readonly client = new ECRClient({});
|
private readonly client = new ECRClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getRepositories = async () => {
|
private readonly getRepositories = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}));
|
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||||
return response.repositories || [];
|
return response.repositories || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECRPrivateImageScanningEnabled',
|
name: 'ECRPrivateImageScanningEnabled',
|
||||||
description: 'Ensures that image scanning on push is enabled for private ECR repositories.',
|
description: 'Ensures that image scanning on push is enabled for private ECR repositories.',
|
||||||
priority: 2,
|
priority: 2,
|
||||||
priorityReason:
|
priorityReason: 'Enabling image scanning on push helps identify vulnerabilities in container images during upload.',
|
||||||
'Enabling image scanning on push helps identify vulnerabilities in container images during upload.',
|
|
||||||
awsService: 'ECR',
|
awsService: 'ECR',
|
||||||
awsServiceCategory: 'Container',
|
awsServiceCategory: 'Container',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -29,90 +24,90 @@ export class ECRPrivateImageScanningEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeRepositoriesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutImageScanningConfigurationCommand',
|
name: 'PutImageScanningConfigurationCommand',
|
||||||
reason: 'Enable image scanning on push for non-compliant repositories.',
|
reason: 'Enable image scanning on push for non-compliant repositories.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const repositories = await this.getRepositories();
|
const repositories = await this.getRepositories()
|
||||||
|
|
||||||
for (const repository of repositories) {
|
for (const repository of repositories) {
|
||||||
if (repository.imageScanningConfiguration?.scanOnPush) {
|
if (repository.imageScanningConfiguration?.scanOnPush) {
|
||||||
compliantResources.push(repository.repositoryArn!);
|
compliantResources.push(repository.repositoryArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(repository.repositoryArn!);
|
nonCompliantResources.push(repository.repositoryArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const repositoryName = arn.split('/').pop()!;
|
const repositoryName = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutImageScanningConfigurationCommand({
|
new PutImageScanningConfigurationCommand({
|
||||||
repositoryName,
|
repositoryName,
|
||||||
imageScanningConfiguration: { scanOnPush: true },
|
imageScanningConfiguration: { scanOnPush: true }
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,19 @@ import {
|
|||||||
ECRClient,
|
ECRClient,
|
||||||
DescribeRepositoriesCommand,
|
DescribeRepositoriesCommand,
|
||||||
PutLifecyclePolicyCommand,
|
PutLifecyclePolicyCommand,
|
||||||
GetLifecyclePolicyCommand,
|
GetLifecyclePolicyCommand
|
||||||
} from '@aws-sdk/client-ecr';
|
} from '@aws-sdk/client-ecr'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECRPrivateLifecyclePolicyConfigured implements BPSet {
|
export class ECRPrivateLifecyclePolicyConfigured implements BPSet {
|
||||||
private readonly client = new ECRClient({});
|
private readonly client = new ECRClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getRepositories = async () => {
|
private readonly getRepositories = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}));
|
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||||
return response.repositories || [];
|
return response.repositories || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECRPrivateLifecyclePolicyConfigured',
|
name: 'ECRPrivateLifecyclePolicyConfigured',
|
||||||
@ -30,119 +30,118 @@ export class ECRPrivateLifecyclePolicyConfigured implements BPSet {
|
|||||||
name: 'lifecycle-policy',
|
name: 'lifecycle-policy',
|
||||||
description: 'The JSON-formatted lifecycle policy text to apply to the repositories.',
|
description: 'The JSON-formatted lifecycle policy text to apply to the repositories.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeRepositoriesCommand',
|
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',
|
name: 'GetLifecyclePolicyCommand',
|
||||||
reason: 'Verify if a lifecycle policy exists for each repository.',
|
reason: 'Verify if a lifecycle policy exists for each repository.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutLifecyclePolicyCommand',
|
name: 'PutLifecyclePolicyCommand',
|
||||||
reason: 'Apply a lifecycle policy to non-compliant ECR repositories.',
|
reason: 'Apply a lifecycle policy to non-compliant ECR repositories.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const repositories = await this.getRepositories();
|
const repositories = await this.getRepositories()
|
||||||
|
|
||||||
for (const repository of repositories) {
|
for (const repository of repositories) {
|
||||||
try {
|
try {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new GetLifecyclePolicyCommand({
|
new GetLifecyclePolicyCommand({
|
||||||
registryId: repository.registryId,
|
registryId: repository.registryId,
|
||||||
repositoryName: repository.repositoryName,
|
repositoryName: repository.repositoryName
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
compliantResources.push(repository.repositoryArn!);
|
compliantResources.push(repository.repositoryArn!)
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
if (error.name === 'LifecyclePolicyNotFoundException') {
|
if (error.name === 'LifecyclePolicyNotFoundException') {
|
||||||
nonCompliantResources.push(repository.repositoryArn!);
|
nonCompliantResources.push(repository.repositoryArn!)
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const lifecyclePolicy = requiredParametersForFix.find(
|
const lifecyclePolicy = requiredParametersForFix.find((param) => param.name === 'lifecycle-policy')?.value
|
||||||
(param) => param.name === 'lifecycle-policy'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!lifecyclePolicy) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const repositoryName = arn.split('/').pop()!;
|
const repositoryName = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutLifecyclePolicyCommand({
|
new PutLifecyclePolicyCommand({
|
||||||
repositoryName,
|
repositoryName,
|
||||||
lifecyclePolicyText: lifecyclePolicy,
|
lifecyclePolicyText: lifecyclePolicy
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
import {
|
import { ECRClient, DescribeRepositoriesCommand, PutImageTagMutabilityCommand } from '@aws-sdk/client-ecr'
|
||||||
ECRClient,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeRepositoriesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
PutImageTagMutabilityCommand,
|
|
||||||
} from '@aws-sdk/client-ecr';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class ECRPrivateTagImmutabilityEnabled implements BPSet {
|
export class ECRPrivateTagImmutabilityEnabled implements BPSet {
|
||||||
private readonly client = new ECRClient({});
|
private readonly client = new ECRClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getRepositories = async () => {
|
private readonly getRepositories = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}));
|
const response = await this.memoClient.send(new DescribeRepositoriesCommand({}))
|
||||||
return response.repositories || [];
|
return response.repositories || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECRPrivateTagImmutabilityEnabled',
|
name: 'ECRPrivateTagImmutabilityEnabled',
|
||||||
@ -29,91 +25,90 @@ export class ECRPrivateTagImmutabilityEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeRepositoriesCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutImageTagMutabilityCommand',
|
name: 'PutImageTagMutabilityCommand',
|
||||||
reason: 'Set image tag immutability for non-compliant repositories.',
|
reason: 'Set image tag immutability for non-compliant repositories.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const repositories = await this.getRepositories();
|
const repositories = await this.getRepositories()
|
||||||
|
|
||||||
for (const repository of repositories) {
|
for (const repository of repositories) {
|
||||||
if (repository.imageTagMutability === 'IMMUTABLE') {
|
if (repository.imageTagMutability === 'IMMUTABLE') {
|
||||||
compliantResources.push(repository.repositoryArn!);
|
compliantResources.push(repository.repositoryArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(repository.repositoryArn!);
|
nonCompliantResources.push(repository.repositoryArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const repositoryName = arn.split('/').pop()!;
|
const repositoryName = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutImageTagMutabilityCommand({
|
new PutImageTagMutabilityCommand({
|
||||||
repositoryName,
|
repositoryName,
|
||||||
imageTagMutability: 'IMMUTABLE',
|
imageTagMutability: 'IMMUTABLE'
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,35 +2,30 @@ import {
|
|||||||
ECSClient,
|
ECSClient,
|
||||||
ListTaskDefinitionsCommand,
|
ListTaskDefinitionsCommand,
|
||||||
DescribeTaskDefinitionCommand,
|
DescribeTaskDefinitionCommand,
|
||||||
RegisterTaskDefinitionCommand,
|
RegisterTaskDefinitionCommand
|
||||||
} from '@aws-sdk/client-ecs';
|
} from '@aws-sdk/client-ecs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECSAwsVpcNetworkingEnabled implements BPSet {
|
export class ECSAwsVpcNetworkingEnabled implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTaskDefinitions = async () => {
|
private readonly getTaskDefinitions = async () => {
|
||||||
const taskDefinitionArns = await this.memoClient.send(
|
const taskDefinitionArns = await this.memoClient.send(new ListTaskDefinitionsCommand({ status: 'ACTIVE' }))
|
||||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
const taskDefinitions = []
|
||||||
);
|
|
||||||
const taskDefinitions = [];
|
|
||||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||||
);
|
|
||||||
taskDefinitions.push(taskDefinition.taskDefinition!);
|
|
||||||
}
|
}
|
||||||
return taskDefinitions;
|
return taskDefinitions
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSAwsVpcNetworkingEnabled',
|
name: 'ECSAwsVpcNetworkingEnabled',
|
||||||
description: 'Ensures that ECS task definitions are configured to use the awsvpc network mode.',
|
description: 'Ensures that ECS task definitions are configured to use the awsvpc network mode.',
|
||||||
priority: 3,
|
priority: 3,
|
||||||
priorityReason:
|
priorityReason: 'Using the awsvpc network mode provides enhanced security and networking capabilities.',
|
||||||
'Using the awsvpc network mode provides enhanced security and networking capabilities.',
|
|
||||||
awsService: 'ECS',
|
awsService: 'ECS',
|
||||||
awsServiceCategory: 'Container',
|
awsServiceCategory: 'Container',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -39,90 +34,88 @@ export class ECSAwsVpcNetworkingEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTaskDefinitionsCommand',
|
name: 'ListTaskDefinitionsCommand',
|
||||||
reason: 'Retrieve all active ECS task definitions.',
|
reason: 'Retrieve all active ECS task definitions.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeTaskDefinitionCommand',
|
name: 'DescribeTaskDefinitionCommand',
|
||||||
reason: 'Describe details of each ECS task definition.',
|
reason: 'Describe details of each ECS task definition.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RegisterTaskDefinitionCommand',
|
name: 'RegisterTaskDefinitionCommand',
|
||||||
reason: 'Re-register ECS task definitions with awsvpc network mode.',
|
reason: 'Re-register ECS task definitions with awsvpc network mode.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const taskDefinitions = await this.getTaskDefinitions();
|
const taskDefinitions = await this.getTaskDefinitions()
|
||||||
|
|
||||||
for (const taskDefinition of taskDefinitions) {
|
for (const taskDefinition of taskDefinitions) {
|
||||||
if (taskDefinition.networkMode === 'awsvpc') {
|
if (taskDefinition.networkMode === 'awsvpc') {
|
||||||
compliantResources.push(taskDefinition.taskDefinitionArn!);
|
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
|
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
const family = taskDefinition.taskDefinition?.family
|
||||||
);
|
|
||||||
const family = taskDefinition.taskDefinition?.family;
|
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new RegisterTaskDefinitionCommand({
|
new RegisterTaskDefinitionCommand({
|
||||||
@ -131,9 +124,9 @@ export class ECSAwsVpcNetworkingEnabled implements BPSet {
|
|||||||
networkMode: 'awsvpc',
|
networkMode: 'awsvpc',
|
||||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||||
cpu: taskDefinition.taskDefinition?.cpu,
|
cpu: taskDefinition.taskDefinition?.cpu,
|
||||||
memory: taskDefinition.taskDefinition?.memory,
|
memory: taskDefinition.taskDefinition?.memory
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,21 @@
|
|||||||
import {
|
import { ECSClient, DescribeClustersCommand, UpdateClusterSettingsCommand } from '@aws-sdk/client-ecs'
|
||||||
ECSClient,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
UpdateClusterSettingsCommand,
|
|
||||||
} from '@aws-sdk/client-ecs';
|
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class ECSContainerInsightsEnabled implements BPSet {
|
export class ECSContainerInsightsEnabled implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getClusters = async () => {
|
private readonly getClusters = async () => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new DescribeClustersCommand({ include: ['SETTINGS'] }))
|
||||||
new DescribeClustersCommand({ include: ['SETTINGS'] })
|
return response.clusters || []
|
||||||
);
|
}
|
||||||
return response.clusters || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSContainerInsightsEnabled',
|
name: 'ECSContainerInsightsEnabled',
|
||||||
description: 'Ensures that ECS clusters have Container Insights enabled.',
|
description: 'Ensures that ECS clusters have Container Insights enabled.',
|
||||||
priority: 3,
|
priority: 3,
|
||||||
priorityReason:
|
priorityReason: 'Enabling Container Insights provides enhanced monitoring and diagnostics for ECS clusters.',
|
||||||
'Enabling Container Insights provides enhanced monitoring and diagnostics for ECS clusters.',
|
|
||||||
awsService: 'ECS',
|
awsService: 'ECS',
|
||||||
awsServiceCategory: 'Container',
|
awsServiceCategory: 'Container',
|
||||||
bestPracticeCategory: 'Monitoring',
|
bestPracticeCategory: 'Monitoring',
|
||||||
@ -31,94 +24,91 @@ export class ECSContainerInsightsEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeClustersCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateClusterSettingsCommand',
|
name: 'UpdateClusterSettingsCommand',
|
||||||
reason: 'Enable Container Insights for non-compliant ECS clusters.',
|
reason: 'Enable Container Insights for non-compliant ECS clusters.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const clusters = await this.getClusters();
|
const clusters = await this.getClusters()
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
if (cluster.clusterName === 'default')
|
if (cluster.clusterName === 'default') continue
|
||||||
continue
|
|
||||||
|
|
||||||
const containerInsightsSetting = cluster.settings?.find(
|
const containerInsightsSetting = cluster.settings?.find((setting) => setting.name === 'containerInsights')
|
||||||
(setting) => setting.name === 'containerInsights'
|
|
||||||
);
|
|
||||||
if (containerInsightsSetting?.value === 'enabled') {
|
if (containerInsightsSetting?.value === 'enabled') {
|
||||||
compliantResources.push(cluster.clusterArn!);
|
compliantResources.push(cluster.clusterArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.clusterArn!);
|
nonCompliantResources.push(cluster.clusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateClusterSettingsCommand({
|
new UpdateClusterSettingsCommand({
|
||||||
cluster: arn,
|
cluster: arn,
|
||||||
settings: [{ name: 'containerInsights', value: 'enabled' }],
|
settings: [{ name: 'containerInsights', value: 'enabled' }]
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,30 @@ import {
|
|||||||
ECSClient,
|
ECSClient,
|
||||||
DescribeTaskDefinitionCommand,
|
DescribeTaskDefinitionCommand,
|
||||||
RegisterTaskDefinitionCommand,
|
RegisterTaskDefinitionCommand,
|
||||||
ListTaskDefinitionsCommand,
|
ListTaskDefinitionsCommand
|
||||||
} from '@aws-sdk/client-ecs';
|
} from '@aws-sdk/client-ecs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECSContainersNonPrivileged implements BPSet {
|
export class ECSContainersNonPrivileged implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTaskDefinitions = async () => {
|
private readonly getTaskDefinitions = async () => {
|
||||||
const taskDefinitionArns = await this.memoClient.send(
|
const taskDefinitionArns = await this.memoClient.send(new ListTaskDefinitionsCommand({ status: 'ACTIVE' }))
|
||||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
const taskDefinitions = []
|
||||||
);
|
|
||||||
const taskDefinitions = [];
|
|
||||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||||
);
|
|
||||||
taskDefinitions.push(taskDefinition.taskDefinition!);
|
|
||||||
}
|
}
|
||||||
return taskDefinitions;
|
return taskDefinitions
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSContainersNonPrivileged',
|
name: 'ECSContainersNonPrivileged',
|
||||||
description: 'Ensures that containers in ECS task definitions are not running in privileged mode.',
|
description: 'Ensures that containers in ECS task definitions are not running in privileged mode.',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
priorityReason:
|
priorityReason: 'Running containers in privileged mode poses significant security risks.',
|
||||||
'Running containers in privileged mode poses significant security risks.',
|
|
||||||
awsService: 'ECS',
|
awsService: 'ECS',
|
||||||
awsServiceCategory: 'Container',
|
awsServiceCategory: 'Container',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -39,100 +34,93 @@ export class ECSContainersNonPrivileged implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTaskDefinitionsCommand',
|
name: 'ListTaskDefinitionsCommand',
|
||||||
reason: 'Retrieve all active ECS task definitions.',
|
reason: 'Retrieve all active ECS task definitions.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeTaskDefinitionCommand',
|
name: 'DescribeTaskDefinitionCommand',
|
||||||
reason: 'Check the container configurations in ECS task definitions.',
|
reason: 'Check the container configurations in ECS task definitions.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RegisterTaskDefinitionCommand',
|
name: 'RegisterTaskDefinitionCommand',
|
||||||
reason: 'Re-register ECS task definitions with privileged mode disabled.',
|
reason: 'Re-register ECS task definitions with privileged mode disabled.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure that containers do not rely on privileged mode for their functionality.'
|
||||||
'Ensure that containers do not rely on privileged mode for their functionality.',
|
})
|
||||||
});
|
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const taskDefinitions = await this.getTaskDefinitions();
|
const taskDefinitions = await this.getTaskDefinitions()
|
||||||
|
|
||||||
for (const taskDefinition of taskDefinitions) {
|
for (const taskDefinition of taskDefinitions) {
|
||||||
const privilegedContainers = taskDefinition.containerDefinitions?.filter(
|
const privilegedContainers = taskDefinition.containerDefinitions?.filter((container) => container.privileged)
|
||||||
(container) => container.privileged
|
|
||||||
);
|
|
||||||
if (privilegedContainers?.length) {
|
if (privilegedContainers?.length) {
|
||||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
|
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(taskDefinition.taskDefinitionArn!);
|
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
const family = taskDefinition.taskDefinition?.family
|
||||||
);
|
|
||||||
const family = taskDefinition.taskDefinition?.family;
|
|
||||||
|
|
||||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
|
||||||
(container) => ({
|
...container,
|
||||||
...container,
|
privileged: false
|
||||||
privileged: false,
|
}))
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new RegisterTaskDefinitionCommand({
|
new RegisterTaskDefinitionCommand({
|
||||||
@ -141,9 +129,9 @@ export class ECSContainersNonPrivileged implements BPSet {
|
|||||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||||
cpu: taskDefinition.taskDefinition?.cpu,
|
cpu: taskDefinition.taskDefinition?.cpu,
|
||||||
memory: taskDefinition.taskDefinition?.memory,
|
memory: taskDefinition.taskDefinition?.memory
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,24 @@ import {
|
|||||||
ECSClient,
|
ECSClient,
|
||||||
DescribeTaskDefinitionCommand,
|
DescribeTaskDefinitionCommand,
|
||||||
RegisterTaskDefinitionCommand,
|
RegisterTaskDefinitionCommand,
|
||||||
ListTaskDefinitionsCommand,
|
ListTaskDefinitionsCommand
|
||||||
} from '@aws-sdk/client-ecs';
|
} from '@aws-sdk/client-ecs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECSContainersReadonlyAccess implements BPSet {
|
export class ECSContainersReadonlyAccess implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTaskDefinitions = async () => {
|
private readonly getTaskDefinitions = async () => {
|
||||||
const taskDefinitionArns = await this.memoClient.send(
|
const taskDefinitionArns = await this.memoClient.send(new ListTaskDefinitionsCommand({ status: 'ACTIVE' }))
|
||||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
const taskDefinitions = []
|
||||||
);
|
|
||||||
const taskDefinitions = [];
|
|
||||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||||
);
|
|
||||||
taskDefinitions.push(taskDefinition.taskDefinition!);
|
|
||||||
}
|
}
|
||||||
return taskDefinitions;
|
return taskDefinitions
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSContainersReadonlyAccess',
|
name: 'ECSContainersReadonlyAccess',
|
||||||
@ -39,100 +35,96 @@ export class ECSContainersReadonlyAccess implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTaskDefinitionsCommand',
|
name: 'ListTaskDefinitionsCommand',
|
||||||
reason: 'Retrieve all active ECS task definitions.',
|
reason: 'Retrieve all active ECS task definitions.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeTaskDefinitionCommand',
|
name: 'DescribeTaskDefinitionCommand',
|
||||||
reason: 'Check the container configurations in ECS task definitions.',
|
reason: 'Check the container configurations in ECS task definitions.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RegisterTaskDefinitionCommand',
|
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:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const taskDefinitions = await this.getTaskDefinitions();
|
const taskDefinitions = await this.getTaskDefinitions()
|
||||||
|
|
||||||
for (const taskDefinition of taskDefinitions) {
|
for (const taskDefinition of taskDefinitions) {
|
||||||
const notReadonlyContainers = taskDefinition.containerDefinitions?.filter(
|
const notReadonlyContainers = taskDefinition.containerDefinitions?.filter(
|
||||||
(container) => !container.readonlyRootFilesystem
|
(container) => !container.readonlyRootFilesystem
|
||||||
);
|
)
|
||||||
if (notReadonlyContainers?.length) {
|
if (notReadonlyContainers?.length) {
|
||||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
|
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(taskDefinition.taskDefinitionArn!);
|
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
const family = taskDefinition.taskDefinition?.family
|
||||||
);
|
|
||||||
const family = taskDefinition.taskDefinition?.family;
|
|
||||||
|
|
||||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
|
||||||
(container) => ({
|
...container,
|
||||||
...container,
|
readonlyRootFilesystem: true
|
||||||
readonlyRootFilesystem: true,
|
}))
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new RegisterTaskDefinitionCommand({
|
new RegisterTaskDefinitionCommand({
|
||||||
@ -141,9 +133,9 @@ export class ECSContainersReadonlyAccess implements BPSet {
|
|||||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||||
cpu: taskDefinition.taskDefinition?.cpu,
|
cpu: taskDefinition.taskDefinition?.cpu,
|
||||||
memory: taskDefinition.taskDefinition?.memory,
|
memory: taskDefinition.taskDefinition?.memory
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,31 +3,29 @@ import {
|
|||||||
ListClustersCommand,
|
ListClustersCommand,
|
||||||
ListServicesCommand,
|
ListServicesCommand,
|
||||||
DescribeServicesCommand,
|
DescribeServicesCommand,
|
||||||
UpdateServiceCommand,
|
UpdateServiceCommand
|
||||||
} from '@aws-sdk/client-ecs';
|
} from '@aws-sdk/client-ecs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECSFargateLatestPlatformVersion implements BPSet {
|
export class ECSFargateLatestPlatformVersion implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getServices = async () => {
|
private readonly getServices = async () => {
|
||||||
const clustersResponse = await this.memoClient.send(new ListClustersCommand({}));
|
const clustersResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||||
const clusterArns = clustersResponse.clusterArns || [];
|
const clusterArns = clustersResponse.clusterArns || []
|
||||||
const services: { clusterArn: string; serviceArn: string }[] = [];
|
const services: { clusterArn: string; serviceArn: string }[] = []
|
||||||
|
|
||||||
for (const clusterArn of clusterArns) {
|
for (const clusterArn of clusterArns) {
|
||||||
const servicesResponse = await this.memoClient.send(
|
const servicesResponse = await this.memoClient.send(new ListServicesCommand({ cluster: clusterArn }))
|
||||||
new ListServicesCommand({ cluster: clusterArn })
|
|
||||||
);
|
|
||||||
for (const serviceArn of servicesResponse.serviceArns || []) {
|
for (const serviceArn of servicesResponse.serviceArns || []) {
|
||||||
services.push({ clusterArn, serviceArn });
|
services.push({ clusterArn, serviceArn })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return services;
|
return services
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSFargateLatestPlatformVersion',
|
name: 'ECSFargateLatestPlatformVersion',
|
||||||
@ -43,104 +41,106 @@ export class ECSFargateLatestPlatformVersion implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListClustersCommand',
|
name: 'ListClustersCommand',
|
||||||
reason: 'Retrieve ECS clusters to identify associated services.',
|
reason: 'Retrieve ECS clusters to identify associated services.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ListServicesCommand',
|
name: 'ListServicesCommand',
|
||||||
reason: 'Retrieve services associated with each ECS cluster.',
|
reason: 'Retrieve services associated with each ECS cluster.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeServicesCommand',
|
name: 'DescribeServicesCommand',
|
||||||
reason: 'Check the platform version of each ECS service.',
|
reason: 'Check the platform version of each ECS service.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateServiceCommand',
|
name: 'UpdateServiceCommand',
|
||||||
reason: 'Update ECS services to use the latest platform version.',
|
reason: 'Update ECS services to use the latest platform version.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const services = await this.getServices();
|
const services = await this.getServices()
|
||||||
|
|
||||||
for (const { clusterArn, serviceArn } of services) {
|
for (const { clusterArn, serviceArn } of services) {
|
||||||
const serviceResponse = await this.memoClient.send(
|
const serviceResponse = await this.memoClient.send(
|
||||||
new DescribeServicesCommand({ cluster: clusterArn, services: [serviceArn] })
|
new DescribeServicesCommand({ cluster: clusterArn, services: [serviceArn] })
|
||||||
);
|
)
|
||||||
|
|
||||||
const service = serviceResponse.services?.[0];
|
const service = serviceResponse.services?.[0]
|
||||||
if (service?.platformVersion === 'LATEST') {
|
if (service?.platformVersion === 'LATEST') {
|
||||||
compliantResources.push(service.serviceArn!);
|
compliantResources.push(service.serviceArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(service?.serviceArn!);
|
if (service?.serviceArn) {
|
||||||
|
nonCompliantResources.push(service.serviceArn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const serviceArn of 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(
|
await this.client.send(
|
||||||
new UpdateServiceCommand({
|
new UpdateServiceCommand({
|
||||||
cluster: clusterArn,
|
cluster: clusterArn,
|
||||||
service: serviceArn,
|
service: serviceArn,
|
||||||
platformVersion: 'LATEST',
|
platformVersion: 'LATEST'
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,30 @@ import {
|
|||||||
ECSClient,
|
ECSClient,
|
||||||
ListTaskDefinitionsCommand,
|
ListTaskDefinitionsCommand,
|
||||||
DescribeTaskDefinitionCommand,
|
DescribeTaskDefinitionCommand,
|
||||||
RegisterTaskDefinitionCommand,
|
RegisterTaskDefinitionCommand
|
||||||
} from '@aws-sdk/client-ecs';
|
} from '@aws-sdk/client-ecs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECSTaskDefinitionLogConfiguration implements BPSet {
|
export class ECSTaskDefinitionLogConfiguration implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTaskDefinitions = async () => {
|
private readonly getTaskDefinitions = async () => {
|
||||||
const taskDefinitionArns = await this.memoClient.send(
|
const taskDefinitionArns = await this.memoClient.send(new ListTaskDefinitionsCommand({ status: 'ACTIVE' }))
|
||||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
const taskDefinitions = []
|
||||||
);
|
|
||||||
const taskDefinitions = [];
|
|
||||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||||
);
|
|
||||||
taskDefinitions.push(taskDefinition.taskDefinition!);
|
|
||||||
}
|
}
|
||||||
return taskDefinitions;
|
return taskDefinitions
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSTaskDefinitionLogConfiguration',
|
name: 'ECSTaskDefinitionLogConfiguration',
|
||||||
description: 'Ensures that ECS task definitions have log configuration enabled.',
|
description: 'Ensures that ECS task definitions have log configuration enabled.',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
priorityReason:
|
priorityReason: 'Enabling log configuration is critical for monitoring and debugging container workloads.',
|
||||||
'Enabling log configuration is critical for monitoring and debugging container workloads.',
|
|
||||||
awsService: 'ECS',
|
awsService: 'ECS',
|
||||||
awsServiceCategory: 'Container',
|
awsServiceCategory: 'Container',
|
||||||
bestPracticeCategory: 'Monitoring',
|
bestPracticeCategory: 'Monitoring',
|
||||||
@ -39,118 +34,109 @@ export class ECSTaskDefinitionLogConfiguration implements BPSet {
|
|||||||
name: 'log-configuration',
|
name: 'log-configuration',
|
||||||
description: 'The log configuration to apply to non-compliant task definitions.',
|
description: 'The log configuration to apply to non-compliant task definitions.',
|
||||||
default: '{}',
|
default: '{}',
|
||||||
example: '{"logDriver": "awslogs", "options": {"awslogs-group": "/ecs/logs"}}',
|
example: '{"logDriver": "awslogs", "options": {"awslogs-group": "/ecs/logs"}}'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTaskDefinitionsCommand',
|
name: 'ListTaskDefinitionsCommand',
|
||||||
reason: 'Retrieve all active ECS task definitions.',
|
reason: 'Retrieve all active ECS task definitions.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeTaskDefinitionCommand',
|
name: 'DescribeTaskDefinitionCommand',
|
||||||
reason: 'Check the container configurations in ECS task definitions.',
|
reason: 'Check the container configurations in ECS task definitions.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RegisterTaskDefinitionCommand',
|
name: 'RegisterTaskDefinitionCommand',
|
||||||
reason: 'Re-register ECS task definitions with updated log configuration.',
|
reason: 'Re-register ECS task definitions with updated log configuration.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const taskDefinitions = await this.getTaskDefinitions();
|
const taskDefinitions = await this.getTaskDefinitions()
|
||||||
|
|
||||||
for (const taskDefinition of taskDefinitions) {
|
for (const taskDefinition of taskDefinitions) {
|
||||||
const logDisabledContainers = taskDefinition.containerDefinitions?.filter(
|
const logDisabledContainers = taskDefinition.containerDefinitions?.filter(
|
||||||
(container) => !container.logConfiguration
|
(container) => !container.logConfiguration
|
||||||
);
|
)
|
||||||
if (logDisabledContainers?.length) {
|
if (logDisabledContainers?.length) {
|
||||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
|
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(taskDefinition.taskDefinitionArn!);
|
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
nonCompliantResources,
|
const logConfiguration = requiredParametersForFix.find((param) => param.name === 'log-configuration')?.value
|
||||||
requiredParametersForFix
|
|
||||||
) => {
|
|
||||||
const logConfiguration = requiredParametersForFix.find(
|
|
||||||
(param) => param.name === 'log-configuration'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!logConfiguration) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
const family = taskDefinition.taskDefinition?.family
|
||||||
);
|
|
||||||
const family = taskDefinition.taskDefinition?.family;
|
|
||||||
|
|
||||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
|
||||||
(container) => ({
|
...container,
|
||||||
...container,
|
logConfiguration: JSON.parse(logConfiguration)
|
||||||
logConfiguration: JSON.parse(logConfiguration),
|
}))
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new RegisterTaskDefinitionCommand({
|
new RegisterTaskDefinitionCommand({
|
||||||
@ -159,9 +145,9 @@ export class ECSTaskDefinitionLogConfiguration implements BPSet {
|
|||||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||||
cpu: taskDefinition.taskDefinition?.cpu,
|
cpu: taskDefinition.taskDefinition?.cpu,
|
||||||
memory: taskDefinition.taskDefinition?.memory,
|
memory: taskDefinition.taskDefinition?.memory
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,24 @@ import {
|
|||||||
ECSClient,
|
ECSClient,
|
||||||
ListTaskDefinitionsCommand,
|
ListTaskDefinitionsCommand,
|
||||||
DescribeTaskDefinitionCommand,
|
DescribeTaskDefinitionCommand,
|
||||||
RegisterTaskDefinitionCommand,
|
RegisterTaskDefinitionCommand
|
||||||
} from '@aws-sdk/client-ecs';
|
} from '@aws-sdk/client-ecs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
|
export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTaskDefinitions = async () => {
|
private readonly getTaskDefinitions = async () => {
|
||||||
const taskDefinitionArns = await this.memoClient.send(
|
const taskDefinitionArns = await this.memoClient.send(new ListTaskDefinitionsCommand({ status: 'ACTIVE' }))
|
||||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
const taskDefinitions = []
|
||||||
);
|
|
||||||
const taskDefinitions = [];
|
|
||||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||||
);
|
|
||||||
taskDefinitions.push(taskDefinition.taskDefinition!);
|
|
||||||
}
|
}
|
||||||
return taskDefinitions;
|
return taskDefinitions
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSTaskDefinitionMemoryHardLimit',
|
name: 'ECSTaskDefinitionMemoryHardLimit',
|
||||||
@ -39,115 +35,105 @@ export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
|
|||||||
name: 'default-memory-limit',
|
name: 'default-memory-limit',
|
||||||
description: 'The default memory hard limit to set for containers without a memory limit.',
|
description: 'The default memory hard limit to set for containers without a memory limit.',
|
||||||
default: '512',
|
default: '512',
|
||||||
example: '1024',
|
example: '1024'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTaskDefinitionsCommand',
|
name: 'ListTaskDefinitionsCommand',
|
||||||
reason: 'Retrieve all active ECS task definitions.',
|
reason: 'Retrieve all active ECS task definitions.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeTaskDefinitionCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RegisterTaskDefinitionCommand',
|
name: 'RegisterTaskDefinitionCommand',
|
||||||
reason: 'Re-register ECS task definitions with updated memory limits.',
|
reason: 'Re-register ECS task definitions with updated memory limits.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure the default memory limit aligns with your container resource requirements.'
|
||||||
'Ensure the default memory limit aligns with your container resource requirements.',
|
})
|
||||||
});
|
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const taskDefinitions = await this.getTaskDefinitions();
|
const taskDefinitions = await this.getTaskDefinitions()
|
||||||
|
|
||||||
for (const taskDefinition of taskDefinitions) {
|
for (const taskDefinition of taskDefinitions) {
|
||||||
const containersWithoutMemoryLimit = taskDefinition.containerDefinitions?.filter(
|
const containersWithoutMemoryLimit = taskDefinition.containerDefinitions?.filter((container) => !container.memory)
|
||||||
(container) => !container.memory
|
|
||||||
);
|
|
||||||
if (containersWithoutMemoryLimit?.length) {
|
if (containersWithoutMemoryLimit?.length) {
|
||||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
|
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(taskDefinition.taskDefinitionArn!);
|
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
nonCompliantResources,
|
|
||||||
requiredParametersForFix
|
|
||||||
) => {
|
|
||||||
const defaultMemoryLimit = parseInt(
|
const defaultMemoryLimit = parseInt(
|
||||||
requiredParametersForFix.find((param) => param.name === 'default-memory-limit')?.value || '512',
|
requiredParametersForFix.find((param) => param.name === 'default-memory-limit')?.value || '512',
|
||||||
10
|
10
|
||||||
);
|
)
|
||||||
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
const family = taskDefinition.taskDefinition?.family
|
||||||
);
|
|
||||||
const family = taskDefinition.taskDefinition?.family;
|
|
||||||
|
|
||||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
|
||||||
(container) => ({
|
...container,
|
||||||
...container,
|
memory: container.memory || defaultMemoryLimit
|
||||||
memory: container.memory || defaultMemoryLimit,
|
}))
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new RegisterTaskDefinitionCommand({
|
new RegisterTaskDefinitionCommand({
|
||||||
@ -156,9 +142,9 @@ export class ECSTaskDefinitionMemoryHardLimit implements BPSet {
|
|||||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||||
cpu: taskDefinition.taskDefinition?.cpu,
|
cpu: taskDefinition.taskDefinition?.cpu,
|
||||||
memory: taskDefinition.taskDefinition?.memory,
|
memory: taskDefinition.taskDefinition?.memory
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,24 @@ import {
|
|||||||
ECSClient,
|
ECSClient,
|
||||||
ListTaskDefinitionsCommand,
|
ListTaskDefinitionsCommand,
|
||||||
DescribeTaskDefinitionCommand,
|
DescribeTaskDefinitionCommand,
|
||||||
RegisterTaskDefinitionCommand,
|
RegisterTaskDefinitionCommand
|
||||||
} from '@aws-sdk/client-ecs';
|
} from '@aws-sdk/client-ecs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ECSTaskDefinitionNonRootUser implements BPSet {
|
export class ECSTaskDefinitionNonRootUser implements BPSet {
|
||||||
private readonly client = new ECSClient({});
|
private readonly client = new ECSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getTaskDefinitions = async () => {
|
private readonly getTaskDefinitions = async () => {
|
||||||
const taskDefinitionArns = await this.memoClient.send(
|
const taskDefinitionArns = await this.memoClient.send(new ListTaskDefinitionsCommand({ status: 'ACTIVE' }))
|
||||||
new ListTaskDefinitionsCommand({ status: 'ACTIVE' })
|
const taskDefinitions = []
|
||||||
);
|
|
||||||
const taskDefinitions = [];
|
|
||||||
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
for (const arn of taskDefinitionArns.taskDefinitionArns || []) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
taskDefinitions.push(taskDefinition.taskDefinition!)
|
||||||
);
|
|
||||||
taskDefinitions.push(taskDefinition.taskDefinition!);
|
|
||||||
}
|
}
|
||||||
return taskDefinitions;
|
return taskDefinitions
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ECSTaskDefinitionNonRootUser',
|
name: 'ECSTaskDefinitionNonRootUser',
|
||||||
@ -39,114 +35,106 @@ export class ECSTaskDefinitionNonRootUser implements BPSet {
|
|||||||
name: 'default-non-root-user',
|
name: 'default-non-root-user',
|
||||||
description: 'The default non-root user to assign for containers without a specified user.',
|
description: 'The default non-root user to assign for containers without a specified user.',
|
||||||
default: 'ecs-user',
|
default: 'ecs-user',
|
||||||
example: 'app-user',
|
example: 'app-user'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListTaskDefinitionsCommand',
|
name: 'ListTaskDefinitionsCommand',
|
||||||
reason: 'Retrieve all active ECS task definitions.',
|
reason: 'Retrieve all active ECS task definitions.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeTaskDefinitionCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RegisterTaskDefinitionCommand',
|
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:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const taskDefinitions = await this.getTaskDefinitions();
|
const taskDefinitions = await this.getTaskDefinitions()
|
||||||
|
|
||||||
for (const taskDefinition of taskDefinitions) {
|
for (const taskDefinition of taskDefinitions) {
|
||||||
const privilegedContainers = taskDefinition.containerDefinitions?.filter(
|
const privilegedContainers = taskDefinition.containerDefinitions?.filter(
|
||||||
(container) => !container.user || container.user === 'root'
|
(container) => !container.user || container.user === 'root'
|
||||||
);
|
)
|
||||||
if (privilegedContainers?.length) {
|
if (privilegedContainers?.length) {
|
||||||
nonCompliantResources.push(taskDefinition.taskDefinitionArn!);
|
nonCompliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(taskDefinition.taskDefinitionArn!);
|
compliantResources.push(taskDefinition.taskDefinitionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
nonCompliantResources,
|
const defaultNonRootUser =
|
||||||
requiredParametersForFix
|
requiredParametersForFix.find((param) => param.name === 'default-non-root-user')?.value || 'ecs-user'
|
||||||
) => {
|
|
||||||
const defaultNonRootUser = requiredParametersForFix.find(
|
|
||||||
(param) => param.name === 'default-non-root-user'
|
|
||||||
)?.value || 'ecs-user';
|
|
||||||
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const taskDefinition = await this.memoClient.send(
|
const taskDefinition = await this.memoClient.send(new DescribeTaskDefinitionCommand({ taskDefinition: arn }))
|
||||||
new DescribeTaskDefinitionCommand({ taskDefinition: arn })
|
const family = taskDefinition.taskDefinition?.family
|
||||||
);
|
|
||||||
const family = taskDefinition.taskDefinition?.family;
|
|
||||||
|
|
||||||
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map(
|
const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map((container) => ({
|
||||||
(container) => ({
|
...container,
|
||||||
...container,
|
user: container.user || defaultNonRootUser
|
||||||
user: container.user || defaultNonRootUser,
|
}))
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new RegisterTaskDefinitionCommand({
|
new RegisterTaskDefinitionCommand({
|
||||||
@ -155,9 +143,9 @@ export class ECSTaskDefinitionNonRootUser implements BPSet {
|
|||||||
networkMode: taskDefinition.taskDefinition?.networkMode,
|
networkMode: taskDefinition.taskDefinition?.networkMode,
|
||||||
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities,
|
||||||
cpu: taskDefinition.taskDefinition?.cpu,
|
cpu: taskDefinition.taskDefinition?.cpu,
|
||||||
memory: taskDefinition.taskDefinition?.memory,
|
memory: taskDefinition.taskDefinition?.memory
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,19 @@ import {
|
|||||||
EFSClient,
|
EFSClient,
|
||||||
DescribeAccessPointsCommand,
|
DescribeAccessPointsCommand,
|
||||||
DeleteAccessPointCommand,
|
DeleteAccessPointCommand,
|
||||||
CreateAccessPointCommand,
|
CreateAccessPointCommand
|
||||||
} from '@aws-sdk/client-efs';
|
} from '@aws-sdk/client-efs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class EFSAccessPointEnforceRootDirectory implements BPSet {
|
export class EFSAccessPointEnforceRootDirectory implements BPSet {
|
||||||
private readonly client = new EFSClient({});
|
private readonly client = new EFSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getAccessPoints = async () => {
|
private readonly getAccessPoints = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}));
|
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}))
|
||||||
return response.AccessPoints || [];
|
return response.AccessPoints || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EFSAccessPointEnforceRootDirectory',
|
name: 'EFSAccessPointEnforceRootDirectory',
|
||||||
@ -30,121 +30,116 @@ export class EFSAccessPointEnforceRootDirectory implements BPSet {
|
|||||||
name: 'root-directory-path',
|
name: 'root-directory-path',
|
||||||
description: 'The root directory path to enforce for EFS Access Points.',
|
description: 'The root directory path to enforce for EFS Access Points.',
|
||||||
default: '/',
|
default: '/',
|
||||||
example: '/data',
|
example: '/data'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: true,
|
isFixFunctionUsesDestructiveCommand: true,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeAccessPointsCommand',
|
name: 'DescribeAccessPointsCommand',
|
||||||
reason: 'Retrieve all existing EFS Access Points and their configurations.',
|
reason: 'Retrieve all existing EFS Access Points and their configurations.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'DeleteAccessPointCommand',
|
name: 'DeleteAccessPointCommand',
|
||||||
reason: 'Delete non-compliant EFS Access Points.',
|
reason: 'Delete non-compliant EFS Access Points.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'CreateAccessPointCommand',
|
name: 'CreateAccessPointCommand',
|
||||||
reason: 'Recreate EFS Access Points with the enforced root directory.',
|
reason: 'Recreate EFS Access Points with the enforced root directory.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const accessPoints = await this.getAccessPoints();
|
const accessPoints = await this.getAccessPoints()
|
||||||
|
|
||||||
for (const accessPoint of accessPoints) {
|
for (const accessPoint of accessPoints) {
|
||||||
if (accessPoint.RootDirectory?.Path === '/') {
|
if (accessPoint.RootDirectory?.Path === '/') {
|
||||||
compliantResources.push(accessPoint.AccessPointArn!);
|
compliantResources.push(accessPoint.AccessPointArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(accessPoint.AccessPointArn!);
|
nonCompliantResources.push(accessPoint.AccessPointArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
nonCompliantResources,
|
const rootDirectoryPath = requiredParametersForFix.find((param) => param.name === 'root-directory-path')?.value
|
||||||
requiredParametersForFix
|
|
||||||
) => {
|
|
||||||
const rootDirectoryPath = requiredParametersForFix.find(
|
|
||||||
(param) => param.name === 'root-directory-path'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!rootDirectoryPath) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const accessPointId = arn.split('/').pop()!;
|
const accessPointId = arn.split('/').pop()!
|
||||||
const fileSystemId = arn.split(':file-system/')[1].split('/')[0];
|
const fileSystemId = arn.split(':file-system/')[1].split('/')[0]
|
||||||
|
|
||||||
// Delete the existing access point
|
// Delete the existing access point
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new DeleteAccessPointCommand({
|
new DeleteAccessPointCommand({
|
||||||
AccessPointId: accessPointId,
|
AccessPointId: accessPointId
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Recreate the access point with the desired root directory
|
// Recreate the access point with the desired root directory
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new CreateAccessPointCommand({
|
new CreateAccessPointCommand({
|
||||||
FileSystemId: fileSystemId,
|
FileSystemId: fileSystemId,
|
||||||
RootDirectory: { Path: rootDirectoryPath },
|
RootDirectory: { Path: rootDirectoryPath }
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,19 @@ import {
|
|||||||
EFSClient,
|
EFSClient,
|
||||||
DescribeAccessPointsCommand,
|
DescribeAccessPointsCommand,
|
||||||
DeleteAccessPointCommand,
|
DeleteAccessPointCommand,
|
||||||
CreateAccessPointCommand,
|
CreateAccessPointCommand
|
||||||
} from '@aws-sdk/client-efs';
|
} from '@aws-sdk/client-efs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class EFSAccessPointEnforceUserIdentity implements BPSet {
|
export class EFSAccessPointEnforceUserIdentity implements BPSet {
|
||||||
private readonly client = new EFSClient({});
|
private readonly client = new EFSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getAccessPoints = async () => {
|
private readonly getAccessPoints = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}));
|
const response = await this.memoClient.send(new DescribeAccessPointsCommand({}))
|
||||||
return response.AccessPoints || [];
|
return response.AccessPoints || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EFSAccessPointEnforceUserIdentity',
|
name: 'EFSAccessPointEnforceUserIdentity',
|
||||||
@ -30,119 +30,116 @@ export class EFSAccessPointEnforceUserIdentity implements BPSet {
|
|||||||
name: 'posix-user',
|
name: 'posix-user',
|
||||||
description: 'The PosixUser configuration to enforce for EFS Access Points.',
|
description: 'The PosixUser configuration to enforce for EFS Access Points.',
|
||||||
default: '{"Uid": "1000", "Gid": "1000"}',
|
default: '{"Uid": "1000", "Gid": "1000"}',
|
||||||
example: '{"Uid": "1234", "Gid": "1234"}',
|
example: '{"Uid": "1234", "Gid": "1234"}'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: true,
|
isFixFunctionUsesDestructiveCommand: true,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeAccessPointsCommand',
|
name: 'DescribeAccessPointsCommand',
|
||||||
reason: 'Retrieve all existing EFS Access Points and their configurations.',
|
reason: 'Retrieve all existing EFS Access Points and their configurations.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'DeleteAccessPointCommand',
|
name: 'DeleteAccessPointCommand',
|
||||||
reason: 'Delete non-compliant EFS Access Points.',
|
reason: 'Delete non-compliant EFS Access Points.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'CreateAccessPointCommand',
|
name: 'CreateAccessPointCommand',
|
||||||
reason: 'Recreate EFS Access Points with the enforced PosixUser.',
|
reason: 'Recreate EFS Access Points with the enforced PosixUser.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const accessPoints = await this.getAccessPoints();
|
const accessPoints = await this.getAccessPoints()
|
||||||
|
|
||||||
for (const accessPoint of accessPoints) {
|
for (const accessPoint of accessPoints) {
|
||||||
if (accessPoint.PosixUser) {
|
if (accessPoint.PosixUser) {
|
||||||
compliantResources.push(accessPoint.AccessPointArn!);
|
compliantResources.push(accessPoint.AccessPointArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(accessPoint.AccessPointArn!);
|
nonCompliantResources.push(accessPoint.AccessPointArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
nonCompliantResources,
|
const posixUser = requiredParametersForFix.find((param) => param.name === 'posix-user')?.value
|
||||||
requiredParametersForFix
|
|
||||||
) => {
|
|
||||||
const posixUser = requiredParametersForFix.find((param) => param.name === 'posix-user')?.value;
|
|
||||||
|
|
||||||
if (!posixUser) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const accessPointId = arn.split('/').pop()!;
|
const accessPointId = arn.split('/').pop()!
|
||||||
const fileSystemId = arn.split(':file-system/')[1].split('/')[0];
|
const fileSystemId = arn.split(':file-system/')[1].split('/')[0]
|
||||||
|
|
||||||
// Delete the existing access point
|
// Delete the existing access point
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new DeleteAccessPointCommand({
|
new DeleteAccessPointCommand({
|
||||||
AccessPointId: accessPointId,
|
AccessPointId: accessPointId
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Recreate the access point with the desired PosixUser
|
// Recreate the access point with the desired PosixUser
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new CreateAccessPointCommand({
|
new CreateAccessPointCommand({
|
||||||
FileSystemId: fileSystemId,
|
FileSystemId: fileSystemId,
|
||||||
PosixUser: JSON.parse(posixUser),
|
PosixUser: JSON.parse(posixUser)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,19 @@ import {
|
|||||||
EFSClient,
|
EFSClient,
|
||||||
DescribeFileSystemsCommand,
|
DescribeFileSystemsCommand,
|
||||||
PutBackupPolicyCommand,
|
PutBackupPolicyCommand,
|
||||||
DescribeBackupPolicyCommand,
|
DescribeBackupPolicyCommand
|
||||||
} from '@aws-sdk/client-efs';
|
} from '@aws-sdk/client-efs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class EFSAutomaticBackupsEnabled implements BPSet {
|
export class EFSAutomaticBackupsEnabled implements BPSet {
|
||||||
private readonly client = new EFSClient({});
|
private readonly client = new EFSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getFileSystems = async () => {
|
private readonly getFileSystems = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}));
|
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}))
|
||||||
return response.FileSystems || [];
|
return response.FileSystems || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EFSAutomaticBackupsEnabled',
|
name: 'EFSAutomaticBackupsEnabled',
|
||||||
@ -30,101 +30,97 @@ export class EFSAutomaticBackupsEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeFileSystemsCommand',
|
name: 'DescribeFileSystemsCommand',
|
||||||
reason: 'Retrieve the list of EFS file systems.',
|
reason: 'Retrieve the list of EFS file systems.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeBackupPolicyCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutBackupPolicyCommand',
|
name: 'PutBackupPolicyCommand',
|
||||||
reason: 'Enable automatic backups for the file system.',
|
reason: 'Enable automatic backups for the file system.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure that enabling backups aligns with the organization’s cost and recovery objectives.'
|
||||||
'Ensure that enabling backups aligns with the organization’s cost and recovery objectives.',
|
})
|
||||||
});
|
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const fileSystems = await this.getFileSystems();
|
const fileSystems = await this.getFileSystems()
|
||||||
|
|
||||||
for (const fileSystem of fileSystems) {
|
for (const fileSystem of fileSystems) {
|
||||||
const response = await this.client.send(
|
const response = await this.client.send(
|
||||||
new DescribeBackupPolicyCommand({ FileSystemId: fileSystem.FileSystemId! })
|
new DescribeBackupPolicyCommand({ FileSystemId: fileSystem.FileSystemId! })
|
||||||
);
|
)
|
||||||
|
|
||||||
if (response.BackupPolicy?.Status === 'ENABLED') {
|
if (response.BackupPolicy?.Status === 'ENABLED') {
|
||||||
compliantResources.push(fileSystem.FileSystemArn!);
|
compliantResources.push(fileSystem.FileSystemArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(fileSystem.FileSystemArn!);
|
nonCompliantResources.push(fileSystem.FileSystemArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
nonCompliantResources,
|
|
||||||
requiredParametersForFix
|
|
||||||
) => {
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const fileSystemId = arn.split('/').pop()!;
|
const fileSystemId = arn.split('/').pop()!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutBackupPolicyCommand({
|
new PutBackupPolicyCommand({
|
||||||
FileSystemId: fileSystemId,
|
FileSystemId: fileSystemId,
|
||||||
BackupPolicy: { Status: 'ENABLED' },
|
BackupPolicy: { Status: 'ENABLED' }
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,19 @@ import {
|
|||||||
EFSClient,
|
EFSClient,
|
||||||
DescribeFileSystemsCommand,
|
DescribeFileSystemsCommand,
|
||||||
CreateFileSystemCommand,
|
CreateFileSystemCommand,
|
||||||
DeleteFileSystemCommand,
|
DeleteFileSystemCommand
|
||||||
} from '@aws-sdk/client-efs';
|
} from '@aws-sdk/client-efs'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types';
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class EFSEncryptedCheck implements BPSet {
|
export class EFSEncryptedCheck implements BPSet {
|
||||||
private readonly client = new EFSClient({});
|
private readonly client = new EFSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getFileSystems = async () => {
|
private readonly getFileSystems = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}));
|
const response = await this.memoClient.send(new DescribeFileSystemsCommand({}))
|
||||||
return response.FileSystems || [];
|
return response.FileSystems || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EFSEncryptedCheck',
|
name: 'EFSEncryptedCheck',
|
||||||
@ -30,100 +30,95 @@ export class EFSEncryptedCheck implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeFileSystemsCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'DeleteFileSystemCommand',
|
name: 'DeleteFileSystemCommand',
|
||||||
reason: 'Delete non-compliant EFS file systems.',
|
reason: 'Delete non-compliant EFS file systems.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'CreateFileSystemCommand',
|
name: 'CreateFileSystemCommand',
|
||||||
reason: 'Recreate EFS file systems with encryption enabled.',
|
reason: 'Recreate EFS file systems with encryption enabled.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const fileSystems = await this.getFileSystems();
|
const fileSystems = await this.getFileSystems()
|
||||||
|
|
||||||
for (const fileSystem of fileSystems) {
|
for (const fileSystem of fileSystems) {
|
||||||
if (fileSystem.Encrypted) {
|
if (fileSystem.Encrypted) {
|
||||||
compliantResources.push(fileSystem.FileSystemArn!);
|
compliantResources.push(fileSystem.FileSystemArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(fileSystem.FileSystemArn!);
|
nonCompliantResources.push(fileSystem.FileSystemArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (...args) => {
|
public readonly fix: BPSetFixFn = async (...args) => {
|
||||||
await this.fixImpl(...args).then(
|
await this.fixImpl(...args).then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fixImpl: BPSetFixFn = async (
|
public readonly fixImpl: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
nonCompliantResources,
|
|
||||||
requiredParametersForFix
|
|
||||||
) => {
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const fileSystemId = arn.split('/').pop()!;
|
const fileSystemId = arn.split('/').pop()!
|
||||||
const fileSystem = await this.memoClient.send(
|
const fileSystem = await this.memoClient.send(new DescribeFileSystemsCommand({ FileSystemId: fileSystemId }))
|
||||||
new DescribeFileSystemsCommand({ FileSystemId: fileSystemId })
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete the non-compliant file system
|
// Delete the non-compliant file system
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new DeleteFileSystemCommand({
|
new DeleteFileSystemCommand({
|
||||||
FileSystemId: fileSystemId,
|
FileSystemId: fileSystemId
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Recreate the file system with encryption enabled
|
// Recreate the file system with encryption enabled
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
@ -131,9 +126,9 @@ export class EFSEncryptedCheck implements BPSet {
|
|||||||
Encrypted: true,
|
Encrypted: true,
|
||||||
PerformanceMode: fileSystem.FileSystems?.[0]?.PerformanceMode,
|
PerformanceMode: fileSystem.FileSystems?.[0]?.PerformanceMode,
|
||||||
ThroughputMode: fileSystem.FileSystems?.[0]?.ThroughputMode,
|
ThroughputMode: fileSystem.FileSystems?.[0]?.ThroughputMode,
|
||||||
ProvisionedThroughputInMibps: fileSystem.FileSystems?.[0]?.ProvisionedThroughputInMibps,
|
ProvisionedThroughputInMibps: fileSystem.FileSystems?.[0]?.ProvisionedThroughputInMibps
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,27 @@
|
|||||||
import {
|
import { EFSClient, DescribeFileSystemsCommand, DescribeMountTargetsCommand } from '@aws-sdk/client-efs'
|
||||||
EFSClient,
|
import { EC2Client, DescribeRouteTablesCommand } from '@aws-sdk/client-ec2'
|
||||||
DescribeFileSystemsCommand,
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
DescribeMountTargetsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
} 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 {
|
export class EFSMountTargetPublicAccessible implements BPSet {
|
||||||
private readonly efsClient = new EFSClient({});
|
private readonly efsClient = new EFSClient({})
|
||||||
private readonly ec2Client = new EC2Client({});
|
private readonly ec2Client = new EC2Client({})
|
||||||
private readonly memoEFSClient = Memorizer.memo(this.efsClient);
|
private readonly memoEFSClient = Memorizer.memo(this.efsClient)
|
||||||
private readonly memoEC2Client = Memorizer.memo(this.ec2Client);
|
private readonly memoEC2Client = Memorizer.memo(this.ec2Client)
|
||||||
|
|
||||||
private readonly getFileSystems = async () => {
|
private readonly getFileSystems = async () => {
|
||||||
const response = await this.memoEFSClient.send(new DescribeFileSystemsCommand({}));
|
const response = await this.memoEFSClient.send(new DescribeFileSystemsCommand({}))
|
||||||
return response.FileSystems || [];
|
return response.FileSystems || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getRoutesForSubnet = async (subnetId: string) => {
|
private readonly getRoutesForSubnet = async (subnetId: string) => {
|
||||||
const response = await this.memoEC2Client.send(
|
const response = await this.memoEC2Client.send(
|
||||||
new DescribeRouteTablesCommand({
|
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 = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EFSMountTargetPublicAccessible',
|
name: 'EFSMountTargetPublicAccessible',
|
||||||
@ -41,92 +37,85 @@ export class EFSMountTargetPublicAccessible implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeFileSystemsCommand',
|
name: 'DescribeFileSystemsCommand',
|
||||||
reason: 'Retrieve the list of EFS file systems.',
|
reason: 'Retrieve the list of EFS file systems.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeMountTargetsCommand',
|
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',
|
name: 'DescribeRouteTablesCommand',
|
||||||
reason: 'Check route tables associated with the mount target subnets.',
|
reason: 'Check route tables associated with the mount target subnets.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [],
|
commandUsedInFixFunction: [],
|
||||||
adviseBeforeFixFunction:
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const fileSystems = await this.getFileSystems();
|
const fileSystems = await this.getFileSystems()
|
||||||
|
|
||||||
for (const fileSystem of fileSystems) {
|
for (const fileSystem of fileSystems) {
|
||||||
const mountTargets = await this.memoEFSClient.send(
|
const mountTargets = await this.memoEFSClient.send(
|
||||||
new DescribeMountTargetsCommand({ FileSystemId: fileSystem.FileSystemId! })
|
new DescribeMountTargetsCommand({ FileSystemId: fileSystem.FileSystemId! })
|
||||||
);
|
)
|
||||||
|
|
||||||
let isNonCompliant = false;
|
let isNonCompliant = false
|
||||||
|
|
||||||
for (const mountTarget of mountTargets.MountTargets || []) {
|
for (const mountTarget of mountTargets.MountTargets || []) {
|
||||||
const routes = await this.getRoutesForSubnet(mountTarget.SubnetId!);
|
const routes = await this.getRoutesForSubnet(mountTarget.SubnetId!)
|
||||||
|
|
||||||
if (
|
if (routes.some((route) => route.DestinationCidrBlock === '0.0.0.0/0' && route.GatewayId?.startsWith('igw-'))) {
|
||||||
routes.some(
|
nonCompliantResources.push(fileSystem.FileSystemArn!)
|
||||||
(route) =>
|
isNonCompliant = true
|
||||||
route.DestinationCidrBlock === '0.0.0.0/0' && route.GatewayId?.startsWith('igw-')
|
break
|
||||||
)
|
|
||||||
) {
|
|
||||||
nonCompliantResources.push(fileSystem.FileSystemArn!);
|
|
||||||
isNonCompliant = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNonCompliant) {
|
if (!isNonCompliant) {
|
||||||
compliantResources.push(fileSystem.FileSystemArn!);
|
compliantResources.push(fileSystem.FileSystemArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async () => {
|
public readonly fix: BPSetFixFn = async () => {
|
||||||
throw new Error(
|
throw new Error('Fixing public accessibility for mount targets requires manual network reconfiguration.')
|
||||||
'Fixing public accessibility for mount targets requires manual network reconfiguration.'
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,27 @@
|
|||||||
import {
|
import { EKSClient, ListClustersCommand, DescribeClusterCommand, UpdateClusterConfigCommand } from '@aws-sdk/client-eks'
|
||||||
EKSClient,
|
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
|
||||||
ListClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
DescribeClusterCommand,
|
|
||||||
UpdateClusterConfigCommand,
|
|
||||||
} from '@aws-sdk/client-eks';
|
|
||||||
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EKSClusterLoggingEnabled implements BPSet {
|
export class EKSClusterLoggingEnabled implements BPSet {
|
||||||
private readonly client = new EKSClient({});
|
private readonly client = new EKSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getClusters = async () => {
|
private readonly getClusters = async () => {
|
||||||
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}));
|
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||||
const clusterNames = clusterNamesResponse.clusters || [];
|
const clusterNames = clusterNamesResponse.clusters || []
|
||||||
const clusters = [];
|
const clusters = []
|
||||||
for (const clusterName of clusterNames) {
|
for (const clusterName of clusterNames) {
|
||||||
const cluster = await this.memoClient.send(
|
const cluster = await this.memoClient.send(new DescribeClusterCommand({ name: clusterName }))
|
||||||
new DescribeClusterCommand({ name: clusterName })
|
clusters.push(cluster.cluster!)
|
||||||
);
|
|
||||||
clusters.push(cluster.cluster!);
|
|
||||||
}
|
}
|
||||||
return clusters;
|
return clusters
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EKSClusterLoggingEnabled',
|
name: 'EKSClusterLoggingEnabled',
|
||||||
description: 'Ensures that all EKS clusters have full logging enabled.',
|
description: 'Ensures that all EKS clusters have full logging enabled.',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
priorityReason:
|
priorityReason: 'Cluster logging is essential for monitoring, debugging, and auditing purposes.',
|
||||||
'Cluster logging is essential for monitoring, debugging, and auditing purposes.',
|
|
||||||
awsService: 'EKS',
|
awsService: 'EKS',
|
||||||
awsServiceCategory: 'Kubernetes Service',
|
awsServiceCategory: 'Kubernetes Service',
|
||||||
bestPracticeCategory: 'Observability',
|
bestPracticeCategory: 'Observability',
|
||||||
@ -38,75 +30,74 @@ export class EKSClusterLoggingEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListClustersCommand',
|
name: 'ListClustersCommand',
|
||||||
reason: 'Retrieve the list of EKS clusters.',
|
reason: 'Retrieve the list of EKS clusters.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeClusterCommand',
|
name: 'DescribeClusterCommand',
|
||||||
reason: 'Fetch details about the EKS cluster, including logging configuration.',
|
reason: 'Fetch details about the EKS cluster, including logging configuration.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateClusterConfigCommand',
|
name: 'UpdateClusterConfigCommand',
|
||||||
reason: 'Enable all logging types for the EKS cluster.',
|
reason: 'Enable all logging types for the EKS cluster.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure that enabling full logging does not generate excessive costs or logs.'
|
||||||
'Ensure that enabling full logging does not generate excessive costs or logs.',
|
})
|
||||||
});
|
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const clusters = await this.getClusters();
|
const clusters = await this.getClusters()
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
const clusterLogging = cluster.logging?.clusterLogging?.[0];
|
const clusterLogging = cluster.logging?.clusterLogging?.[0]
|
||||||
if (clusterLogging?.enabled && clusterLogging.types?.length === 5) {
|
if (clusterLogging?.enabled && clusterLogging.types?.length === 5) {
|
||||||
compliantResources.push(cluster.arn!);
|
compliantResources.push(cluster.arn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.arn!);
|
nonCompliantResources.push(cluster.arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterName = arn.split(':cluster/')[1];
|
const clusterName = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateClusterConfigCommand({
|
new UpdateClusterConfigCommand({
|
||||||
@ -115,12 +106,12 @@ export class EKSClusterLoggingEnabled implements BPSet {
|
|||||||
clusterLogging: [
|
clusterLogging: [
|
||||||
{
|
{
|
||||||
enabled: true,
|
enabled: true,
|
||||||
types: ['api', 'audit', 'authenticator', 'controllerManager', 'scheduler'],
|
types: ['api', 'audit', 'authenticator', 'controllerManager', 'scheduler']
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,34 +2,31 @@ import {
|
|||||||
EKSClient,
|
EKSClient,
|
||||||
ListClustersCommand,
|
ListClustersCommand,
|
||||||
DescribeClusterCommand,
|
DescribeClusterCommand,
|
||||||
AssociateEncryptionConfigCommand,
|
AssociateEncryptionConfigCommand
|
||||||
} from '@aws-sdk/client-eks';
|
} from '@aws-sdk/client-eks'
|
||||||
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
|
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class EKSClusterSecretsEncrypted implements BPSet {
|
export class EKSClusterSecretsEncrypted implements BPSet {
|
||||||
private readonly client = new EKSClient({});
|
private readonly client = new EKSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getClusters = async () => {
|
private readonly getClusters = async () => {
|
||||||
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}));
|
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||||
const clusterNames = clusterNamesResponse.clusters || [];
|
const clusterNames = clusterNamesResponse.clusters || []
|
||||||
const clusters = [];
|
const clusters = []
|
||||||
for (const clusterName of clusterNames) {
|
for (const clusterName of clusterNames) {
|
||||||
const cluster = await this.memoClient.send(
|
const cluster = await this.memoClient.send(new DescribeClusterCommand({ name: clusterName }))
|
||||||
new DescribeClusterCommand({ name: clusterName })
|
clusters.push(cluster.cluster!)
|
||||||
);
|
|
||||||
clusters.push(cluster.cluster!);
|
|
||||||
}
|
}
|
||||||
return clusters;
|
return clusters
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EKSClusterSecretsEncrypted',
|
name: 'EKSClusterSecretsEncrypted',
|
||||||
description: 'Ensures that all EKS clusters have secrets encrypted with a KMS key.',
|
description: 'Ensures that all EKS clusters have secrets encrypted with a KMS key.',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
priorityReason:
|
priorityReason: 'Encrypting secrets ensures the security and compliance of sensitive data in EKS clusters.',
|
||||||
'Encrypting secrets ensures the security and compliance of sensitive data in EKS clusters.',
|
|
||||||
awsService: 'EKS',
|
awsService: 'EKS',
|
||||||
awsServiceCategory: 'Kubernetes Service',
|
awsServiceCategory: 'Kubernetes Service',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -38,88 +35,87 @@ export class EKSClusterSecretsEncrypted implements BPSet {
|
|||||||
name: 'kms-key-id',
|
name: 'kms-key-id',
|
||||||
description: 'The KMS key ARN to enable encryption for EKS secrets.',
|
description: 'The KMS key ARN to enable encryption for EKS secrets.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListClustersCommand',
|
name: 'ListClustersCommand',
|
||||||
reason: 'Retrieve the list of EKS clusters.',
|
reason: 'Retrieve the list of EKS clusters.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeClusterCommand',
|
name: 'DescribeClusterCommand',
|
||||||
reason: 'Fetch details about the EKS cluster, including encryption configuration.',
|
reason: 'Fetch details about the EKS cluster, including encryption configuration.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'AssociateEncryptionConfigCommand',
|
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:
|
adviseBeforeFixFunction: 'Ensure that the specified KMS key is accessible to the EKS service and cluster.'
|
||||||
'Ensure that the specified KMS key is accessible to the EKS service and cluster.',
|
})
|
||||||
});
|
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const clusters = await this.getClusters();
|
const clusters = await this.getClusters()
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
const encryptionConfig = cluster.encryptionConfig?.[0];
|
const encryptionConfig = cluster.encryptionConfig?.[0]
|
||||||
if (encryptionConfig?.resources?.includes('secrets')) {
|
if (encryptionConfig?.resources?.includes('secrets')) {
|
||||||
compliantResources.push(cluster.arn!);
|
compliantResources.push(cluster.arn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.arn!);
|
nonCompliantResources.push(cluster.arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
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) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterName = arn.split(':cluster/')[1];
|
const clusterName = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new AssociateEncryptionConfigCommand({
|
new AssociateEncryptionConfigCommand({
|
||||||
@ -127,11 +123,11 @@ export class EKSClusterSecretsEncrypted implements BPSet {
|
|||||||
encryptionConfig: [
|
encryptionConfig: [
|
||||||
{
|
{
|
||||||
resources: ['secrets'],
|
resources: ['secrets'],
|
||||||
provider: { keyArn: kmsKeyId },
|
provider: { keyArn: kmsKeyId }
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,28 @@
|
|||||||
import {
|
import { EKSClient, ListClustersCommand, DescribeClusterCommand, UpdateClusterConfigCommand } from '@aws-sdk/client-eks'
|
||||||
EKSClient,
|
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
|
||||||
ListClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
DescribeClusterCommand,
|
|
||||||
UpdateClusterConfigCommand,
|
|
||||||
} from '@aws-sdk/client-eks';
|
|
||||||
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class EKSEndpointNoPublicAccess implements BPSet {
|
export class EKSEndpointNoPublicAccess implements BPSet {
|
||||||
private readonly client = new EKSClient({});
|
private readonly client = new EKSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getClusters = async () => {
|
private readonly getClusters = async () => {
|
||||||
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}));
|
const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({}))
|
||||||
const clusterNames = clusterNamesResponse.clusters || [];
|
const clusterNames = clusterNamesResponse.clusters || []
|
||||||
const clusters = [];
|
const clusters = []
|
||||||
for (const clusterName of clusterNames) {
|
for (const clusterName of clusterNames) {
|
||||||
const cluster = await this.memoClient.send(
|
const cluster = await this.memoClient.send(new DescribeClusterCommand({ name: clusterName }))
|
||||||
new DescribeClusterCommand({ name: clusterName })
|
clusters.push(cluster.cluster!)
|
||||||
);
|
|
||||||
clusters.push(cluster.cluster!);
|
|
||||||
}
|
}
|
||||||
return clusters;
|
return clusters
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'EKSEndpointNoPublicAccess',
|
name: 'EKSEndpointNoPublicAccess',
|
||||||
description: 'Ensures EKS cluster endpoint does not have public access enabled.',
|
description: 'Ensures EKS cluster endpoint does not have public access enabled.',
|
||||||
priority: 1,
|
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',
|
awsService: 'EKS',
|
||||||
awsServiceCategory: 'Kubernetes Service',
|
awsServiceCategory: 'Kubernetes Service',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -37,83 +31,84 @@ export class EKSEndpointNoPublicAccess implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListClustersCommand',
|
name: 'ListClustersCommand',
|
||||||
reason: 'Retrieves the list of EKS clusters.',
|
reason: 'Retrieves the list of EKS clusters.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'DescribeClusterCommand',
|
name: 'DescribeClusterCommand',
|
||||||
reason: 'Fetches detailed configuration of each EKS cluster.',
|
reason: 'Fetches detailed configuration of each EKS cluster.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateClusterConfigCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const clusters = await this.getClusters();
|
const clusters = await this.getClusters()
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
const endpointPublicAccess = cluster.resourcesVpcConfig?.endpointPublicAccess;
|
const endpointPublicAccess = cluster.resourcesVpcConfig?.endpointPublicAccess
|
||||||
if (endpointPublicAccess) {
|
if (endpointPublicAccess) {
|
||||||
nonCompliantResources.push(cluster.arn!);
|
nonCompliantResources.push(cluster.arn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(cluster.arn!);
|
compliantResources.push(cluster.arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterName = arn.split(':cluster/')[1];
|
const clusterName = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateClusterConfigCommand({
|
new UpdateClusterConfigCommand({
|
||||||
name: clusterName,
|
name: clusterName,
|
||||||
resourcesVpcConfig: {
|
resourcesVpcConfig: {
|
||||||
endpointPublicAccess: false,
|
endpointPublicAccess: false
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,22 @@
|
|||||||
import {
|
import { ElastiCacheClient, DescribeCacheClustersCommand, ModifyCacheClusterCommand } from '@aws-sdk/client-elasticache'
|
||||||
ElastiCacheClient,
|
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
|
||||||
DescribeCacheClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyCacheClusterCommand,
|
|
||||||
} from '@aws-sdk/client-elasticache';
|
|
||||||
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class ElastiCacheAutoMinorVersionUpgradeCheck implements BPSet {
|
export class ElastiCacheAutoMinorVersionUpgradeCheck implements BPSet {
|
||||||
private readonly client = new ElastiCacheClient({});
|
private readonly client = new ElastiCacheClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getClusters = async () => {
|
private readonly getClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}))
|
||||||
return response.CacheClusters || [];
|
return response.CacheClusters || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ElastiCacheAutoMinorVersionUpgradeCheck',
|
name: 'ElastiCacheAutoMinorVersionUpgradeCheck',
|
||||||
description: 'Ensures that ElastiCache clusters have auto minor version upgrade enabled.',
|
description: 'Ensures that ElastiCache clusters have auto minor version upgrade enabled.',
|
||||||
priority: 2,
|
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',
|
awsService: 'ElastiCache',
|
||||||
awsServiceCategory: 'Cache Service',
|
awsServiceCategory: 'Cache Service',
|
||||||
bestPracticeCategory: 'Reliability',
|
bestPracticeCategory: 'Reliability',
|
||||||
@ -28,75 +25,76 @@ export class ElastiCacheAutoMinorVersionUpgradeCheck implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeCacheClustersCommand',
|
name: 'DescribeCacheClustersCommand',
|
||||||
reason: 'Fetches the list and configurations of ElastiCache clusters.',
|
reason: 'Fetches the list and configurations of ElastiCache clusters.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyCacheClusterCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const clusters = await this.getClusters();
|
const clusters = await this.getClusters()
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
if (cluster.AutoMinorVersionUpgrade) {
|
if (cluster.AutoMinorVersionUpgrade) {
|
||||||
compliantResources.push(cluster.ARN!);
|
compliantResources.push(cluster.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.ARN!);
|
nonCompliantResources.push(cluster.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster:')[1];
|
const clusterId = arn.split(':cluster:')[1]
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyCacheClusterCommand({
|
new ModifyCacheClusterCommand({
|
||||||
CacheClusterId: clusterId,
|
CacheClusterId: clusterId,
|
||||||
AutoMinorVersionUpgrade: true,
|
AutoMinorVersionUpgrade: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
ElastiCacheClient,
|
ElastiCacheClient,
|
||||||
DescribeReplicationGroupsCommand,
|
DescribeReplicationGroupsCommand,
|
||||||
ModifyReplicationGroupCommand,
|
ModifyReplicationGroupCommand
|
||||||
} from '@aws-sdk/client-elasticache';
|
} from '@aws-sdk/client-elasticache'
|
||||||
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
|
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ElastiCacheRedisClusterAutomaticBackupCheck implements BPSet {
|
export class ElastiCacheRedisClusterAutomaticBackupCheck implements BPSet {
|
||||||
private readonly client = new ElastiCacheClient({});
|
private readonly client = new ElastiCacheClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getReplicationGroups = async () => {
|
private readonly getReplicationGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||||
return response.ReplicationGroups || [];
|
return response.ReplicationGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ElastiCacheRedisClusterAutomaticBackupCheck',
|
name: 'ElastiCacheRedisClusterAutomaticBackupCheck',
|
||||||
@ -28,90 +28,89 @@ export class ElastiCacheRedisClusterAutomaticBackupCheck implements BPSet {
|
|||||||
name: 'snapshot-retention-period',
|
name: 'snapshot-retention-period',
|
||||||
description: 'Number of days to retain automatic snapshots.',
|
description: 'Number of days to retain automatic snapshots.',
|
||||||
default: '7',
|
default: '7',
|
||||||
example: '7',
|
example: '7'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeReplicationGroupsCommand',
|
name: 'DescribeReplicationGroupsCommand',
|
||||||
reason: 'Fetches details of replication groups to verify backup settings.',
|
reason: 'Fetches details of replication groups to verify backup settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyReplicationGroupCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const replicationGroups = await this.getReplicationGroups();
|
const replicationGroups = await this.getReplicationGroups()
|
||||||
|
|
||||||
for (const group of replicationGroups) {
|
for (const group of replicationGroups) {
|
||||||
if (group.SnapshottingClusterId) {
|
if (group.SnapshottingClusterId) {
|
||||||
compliantResources.push(group.ARN!);
|
compliantResources.push(group.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(group.ARN!);
|
nonCompliantResources.push(group.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
public readonly fix: BPSetFixFn = async (nonCompliantResources, requiredParametersForFix) => {
|
||||||
const retentionPeriod = requiredParametersForFix.find(
|
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'snapshot-retention-period')?.value
|
||||||
(param) => param.name === 'snapshot-retention-period'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!retentionPeriod) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const groupId = arn.split(':replication-group:')[1];
|
const groupId = arn.split(':replication-group:')[1]
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyReplicationGroupCommand({
|
new ModifyReplicationGroupCommand({
|
||||||
ReplicationGroupId: groupId,
|
ReplicationGroupId: groupId,
|
||||||
SnapshotRetentionLimit: parseInt(retentionPeriod, 10),
|
SnapshotRetentionLimit: parseInt(retentionPeriod, 10)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
ElastiCacheClient,
|
ElastiCacheClient,
|
||||||
DescribeReplicationGroupsCommand,
|
DescribeReplicationGroupsCommand,
|
||||||
ModifyReplicationGroupCommand,
|
ModifyReplicationGroupCommand
|
||||||
} from '@aws-sdk/client-elasticache';
|
} from '@aws-sdk/client-elasticache'
|
||||||
import { BPSet, BPSetStats, BPSetFixFn } from '../../types';
|
import { BPSet, BPSetStats, BPSetFixFn } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ElastiCacheReplGrpAutoFailoverEnabled implements BPSet {
|
export class ElastiCacheReplGrpAutoFailoverEnabled implements BPSet {
|
||||||
private readonly client = new ElastiCacheClient({});
|
private readonly client = new ElastiCacheClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getReplicationGroups = async () => {
|
private readonly getReplicationGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||||
return response.ReplicationGroups || [];
|
return response.ReplicationGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ElastiCacheReplGrpAutoFailoverEnabled',
|
name: 'ElastiCacheReplGrpAutoFailoverEnabled',
|
||||||
@ -28,75 +28,76 @@ export class ElastiCacheReplGrpAutoFailoverEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeReplicationGroupsCommand',
|
name: 'DescribeReplicationGroupsCommand',
|
||||||
reason: 'Fetches replication group details to verify automatic failover settings.',
|
reason: 'Fetches replication group details to verify automatic failover settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyReplicationGroupCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const replicationGroups = await this.getReplicationGroups();
|
const replicationGroups = await this.getReplicationGroups()
|
||||||
|
|
||||||
for (const group of replicationGroups) {
|
for (const group of replicationGroups) {
|
||||||
if (group.AutomaticFailover === 'enabled') {
|
if (group.AutomaticFailover === 'enabled') {
|
||||||
compliantResources.push(group.ARN!);
|
compliantResources.push(group.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(group.ARN!);
|
nonCompliantResources.push(group.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
public readonly fix: BPSetFixFn = async (nonCompliantResources) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const groupId = arn.split(':replication-group:')[1];
|
const groupId = arn.split(':replication-group:')[1]
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyReplicationGroupCommand({
|
new ModifyReplicationGroupCommand({
|
||||||
ReplicationGroupId: groupId,
|
ReplicationGroupId: groupId,
|
||||||
AutomaticFailoverEnabled: true,
|
AutomaticFailoverEnabled: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import {
|
import { ElastiCacheClient, DescribeReplicationGroupsCommand } from '@aws-sdk/client-elasticache'
|
||||||
ElastiCacheClient,
|
import { BPSet, BPSetStats } from '../../types'
|
||||||
DescribeReplicationGroupsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
} from '@aws-sdk/client-elasticache';
|
|
||||||
import { BPSet, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class ElastiCacheReplGrpEncryptedAtRest implements BPSet {
|
export class ElastiCacheReplGrpEncryptedAtRest implements BPSet {
|
||||||
private readonly client = new ElastiCacheClient({});
|
private readonly client = new ElastiCacheClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getReplicationGroups = async () => {
|
private readonly getReplicationGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||||
return response.ReplicationGroups || [];
|
return response.ReplicationGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ElastiCacheReplGrpEncryptedAtRest',
|
name: 'ElastiCacheReplGrpEncryptedAtRest',
|
||||||
@ -27,64 +24,65 @@ export class ElastiCacheReplGrpEncryptedAtRest implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeReplicationGroupsCommand',
|
name: 'DescribeReplicationGroupsCommand',
|
||||||
reason: 'Fetches replication group details to verify encryption settings.',
|
reason: 'Fetches replication group details to verify encryption settings.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [],
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const replicationGroups = await this.getReplicationGroups();
|
const replicationGroups = await this.getReplicationGroups()
|
||||||
|
|
||||||
for (const group of replicationGroups) {
|
for (const group of replicationGroups) {
|
||||||
if (group.AtRestEncryptionEnabled) {
|
if (group.AtRestEncryptionEnabled) {
|
||||||
compliantResources.push(group.ARN!);
|
compliantResources.push(group.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(group.ARN!);
|
nonCompliantResources.push(group.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async () => {
|
public readonly fix = async () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Fixing encryption at rest for replication groups requires recreation. Please create a new replication group with AtRestEncryptionEnabled set to true.'
|
'Fixing encryption at rest for replication groups requires recreation. Please create a new replication group with AtRestEncryptionEnabled set to true.'
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import {
|
import { ElastiCacheClient, DescribeReplicationGroupsCommand } from '@aws-sdk/client-elasticache'
|
||||||
ElastiCacheClient,
|
import { BPSet, BPSetStats } from '../../types'
|
||||||
DescribeReplicationGroupsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
} from '@aws-sdk/client-elasticache';
|
|
||||||
import { BPSet, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class ElastiCacheReplGrpEncryptedInTransit implements BPSet {
|
export class ElastiCacheReplGrpEncryptedInTransit implements BPSet {
|
||||||
private readonly client = new ElastiCacheClient({});
|
private readonly client = new ElastiCacheClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getReplicationGroups = async () => {
|
private readonly getReplicationGroups = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}));
|
const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({}))
|
||||||
return response.ReplicationGroups || [];
|
return response.ReplicationGroups || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ElastiCacheReplGrpEncryptedInTransit',
|
name: 'ElastiCacheReplGrpEncryptedInTransit',
|
||||||
@ -27,64 +24,65 @@ export class ElastiCacheReplGrpEncryptedInTransit implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeReplicationGroupsCommand',
|
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: [],
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const replicationGroups = await this.getReplicationGroups();
|
const replicationGroups = await this.getReplicationGroups()
|
||||||
|
|
||||||
for (const group of replicationGroups) {
|
for (const group of replicationGroups) {
|
||||||
if (group.TransitEncryptionEnabled) {
|
if (group.TransitEncryptionEnabled) {
|
||||||
compliantResources.push(group.ARN!);
|
compliantResources.push(group.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(group.ARN!);
|
nonCompliantResources.push(group.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async () => {
|
public readonly fix = async () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Fixing in-transit encryption for replication groups requires recreation. Please create a new replication group with TransitEncryptionEnabled set to true.'
|
'Fixing in-transit encryption for replication groups requires recreation. Please create a new replication group with TransitEncryptionEnabled set to true.'
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,18 @@ import {
|
|||||||
DescribeCacheClustersCommand,
|
DescribeCacheClustersCommand,
|
||||||
DeleteCacheClusterCommand,
|
DeleteCacheClusterCommand,
|
||||||
CreateCacheClusterCommand
|
CreateCacheClusterCommand
|
||||||
} from '@aws-sdk/client-elasticache';
|
} from '@aws-sdk/client-elasticache'
|
||||||
import { BPSet, BPSetStats } from '../../types';
|
import { BPSet, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class ElastiCacheSubnetGroupCheck implements BPSet {
|
export class ElastiCacheSubnetGroupCheck implements BPSet {
|
||||||
private readonly client = new ElastiCacheClient({});
|
private readonly client = new ElastiCacheClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getClusters = async () => {
|
private readonly getClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeCacheClustersCommand({}))
|
||||||
return response.CacheClusters || [];
|
return response.CacheClusters || []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'ElastiCacheSubnetGroupCheck',
|
name: 'ElastiCacheSubnetGroupCheck',
|
||||||
@ -29,106 +29,103 @@ export class ElastiCacheSubnetGroupCheck implements BPSet {
|
|||||||
name: 'subnet-group-name',
|
name: 'subnet-group-name',
|
||||||
description: 'The name of the desired subnet group to associate with the cluster.',
|
description: 'The name of the desired subnet group to associate with the cluster.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'custom-subnet-group',
|
example: 'custom-subnet-group'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: true,
|
isFixFunctionUsesDestructiveCommand: true,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeCacheClustersCommand',
|
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: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'DeleteCacheClusterCommand',
|
name: 'DeleteCacheClusterCommand',
|
||||||
reason: 'Deletes non-compliant ElastiCache clusters.',
|
reason: 'Deletes non-compliant ElastiCache clusters.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'CreateCacheClusterCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const clusters = await this.getClusters();
|
const clusters = await this.getClusters()
|
||||||
|
|
||||||
for (const cluster of clusters) {
|
for (const cluster of clusters) {
|
||||||
if (cluster.CacheSubnetGroupName !== 'default') {
|
if (cluster.CacheSubnetGroupName !== 'default') {
|
||||||
compliantResources.push(cluster.ARN!);
|
compliantResources.push(cluster.ARN!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.ARN!);
|
nonCompliantResources.push(cluster.ARN!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const subnetGroupName = requiredParametersForFix.find(
|
const subnetGroupName = requiredParametersForFix.find((param) => param.name === 'subnet-group-name')?.value
|
||||||
(param) => param.name === 'subnet-group-name'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!subnetGroupName) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster:')[1];
|
const clusterId = arn.split(':cluster:')[1]
|
||||||
const cluster = await this.memoClient.send(
|
const cluster = await this.memoClient.send(new DescribeCacheClustersCommand({ CacheClusterId: clusterId }))
|
||||||
new DescribeCacheClustersCommand({ CacheClusterId: clusterId })
|
const clusterDetails = cluster.CacheClusters?.[0]
|
||||||
);
|
|
||||||
const clusterDetails = cluster.CacheClusters?.[0];
|
|
||||||
|
|
||||||
if (!clusterDetails) {
|
if (!clusterDetails) {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the non-compliant cluster
|
// Delete the non-compliant cluster
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new DeleteCacheClusterCommand({
|
new DeleteCacheClusterCommand({
|
||||||
CacheClusterId: clusterId,
|
CacheClusterId: clusterId
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Recreate the cluster with the desired subnet group
|
// Recreate the cluster with the desired subnet group
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
@ -138,13 +135,11 @@ export class ElastiCacheSubnetGroupCheck implements BPSet {
|
|||||||
CacheNodeType: clusterDetails.CacheNodeType!,
|
CacheNodeType: clusterDetails.CacheNodeType!,
|
||||||
NumCacheNodes: clusterDetails.NumCacheNodes!,
|
NumCacheNodes: clusterDetails.NumCacheNodes!,
|
||||||
CacheSubnetGroupName: subnetGroupName,
|
CacheSubnetGroupName: subnetGroupName,
|
||||||
SecurityGroupIds: clusterDetails.SecurityGroups?.map(
|
SecurityGroupIds: clusterDetails.SecurityGroups?.map((group) => group.SecurityGroupId) as string[],
|
||||||
(group) => group.SecurityGroupId
|
|
||||||
) as string[],
|
|
||||||
PreferredMaintenanceWindow: clusterDetails.PreferredMaintenanceWindow,
|
PreferredMaintenanceWindow: clusterDetails.PreferredMaintenanceWindow,
|
||||||
EngineVersion: clusterDetails.EngineVersion,
|
EngineVersion: clusterDetails.EngineVersion
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
import {
|
import { IAMClient, ListPoliciesCommand, GetPolicyVersionCommand, DeletePolicyCommand } from '@aws-sdk/client-iam'
|
||||||
IAMClient,
|
import { BPSet, BPSetStats } from '../../types'
|
||||||
ListPoliciesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetPolicyVersionCommand,
|
|
||||||
DeletePolicyCommand
|
|
||||||
} from '@aws-sdk/client-iam';
|
|
||||||
import { BPSet, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class IAMPolicyNoStatementsWithAdminAccess implements BPSet {
|
export class IAMPolicyNoStatementsWithAdminAccess implements BPSet {
|
||||||
private readonly client = new IAMClient({});
|
private readonly client = new IAMClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly getPolicies = async () => {
|
private readonly getPolicies = async () => {
|
||||||
const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }));
|
const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }))
|
||||||
return response.Policies || [];
|
return response.Policies || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getPolicyDefaultVersions = async (policyArn: string, versionId: string) => {
|
private readonly getPolicyDefaultVersions = async (policyArn: string, versionId: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(
|
||||||
new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: versionId })
|
new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: versionId })
|
||||||
);
|
)
|
||||||
return response.PolicyVersion!;
|
return response.PolicyVersion!
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = () => ({
|
public readonly getMetadata = () => ({
|
||||||
name: 'IAMPolicyNoStatementsWithAdminAccess',
|
name: 'IAMPolicyNoStatementsWithAdminAccess',
|
||||||
@ -36,89 +31,83 @@ export class IAMPolicyNoStatementsWithAdminAccess implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListPoliciesCommand',
|
name: 'ListPoliciesCommand',
|
||||||
reason: 'Fetches all local IAM policies.',
|
reason: 'Fetches all local IAM policies.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GetPolicyVersionCommand',
|
name: 'GetPolicyVersionCommand',
|
||||||
reason: 'Retrieves the default version of each policy.',
|
reason: 'Retrieves the default version of each policy.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'DeletePolicyCommand',
|
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 = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl().then(
|
await this.checkImpl().then(
|
||||||
() => (this.stats.status = 'FINISHED'),
|
() => (this.stats.status = 'FINISHED'),
|
||||||
(err) => {
|
(err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const policies = await this.getPolicies();
|
const policies = await this.getPolicies()
|
||||||
|
|
||||||
for (const policy of policies) {
|
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 policyDocument = JSON.parse(JSON.stringify(policyVersion.Document)) // Parse Document JSON string
|
||||||
const statements = Array.isArray(policyDocument.Statement)
|
const statements = Array.isArray(policyDocument.Statement) ? policyDocument.Statement : [policyDocument.Statement]
|
||||||
? policyDocument.Statement
|
|
||||||
: [policyDocument.Statement];
|
|
||||||
|
|
||||||
for (const statement of statements) {
|
for (const statement of statements) {
|
||||||
if (
|
if (statement?.Action === '*' && statement?.Resource === '*' && statement?.Effect === 'Allow') {
|
||||||
statement?.Action === '*' &&
|
nonCompliantResources.push(policy.Arn!)
|
||||||
statement?.Resource === '*' &&
|
break
|
||||||
statement?.Effect === 'Allow'
|
|
||||||
) {
|
|
||||||
nonCompliantResources.push(policy.Arn!);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nonCompliantResources.includes(policy.Arn!)) {
|
if (!nonCompliantResources.includes(policy.Arn!)) {
|
||||||
compliantResources.push(policy.Arn!);
|
compliantResources.push(policy.Arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (nonCompliantResources: string[]) => {
|
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
await this.client.send(new DeletePolicyCommand({ PolicyArn: arn }));
|
await this.client.send(new DeletePolicyCommand({ PolicyArn: arn }))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,185 +3,170 @@ import {
|
|||||||
ListPoliciesCommand,
|
ListPoliciesCommand,
|
||||||
GetPolicyVersionCommand,
|
GetPolicyVersionCommand,
|
||||||
CreatePolicyVersionCommand,
|
CreatePolicyVersionCommand,
|
||||||
DeletePolicyVersionCommand,
|
DeletePolicyVersionCommand
|
||||||
} from "@aws-sdk/client-iam";
|
} from '@aws-sdk/client-iam'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from "../../types";
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from "../../Memorizer";
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class IAMPolicyNoStatementsWithFullAccess implements BPSet {
|
export class IAMPolicyNoStatementsWithFullAccess implements BPSet {
|
||||||
private readonly client = new IAMClient({});
|
private readonly client = new IAMClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: "LOADED",
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: "IAMPolicyNoStatementsWithFullAccess",
|
name: 'IAMPolicyNoStatementsWithFullAccess',
|
||||||
description: "Ensures IAM policies do not have statements granting full access.",
|
description: 'Ensures IAM policies do not have statements granting full access.',
|
||||||
priority: 2,
|
priority: 2,
|
||||||
priorityReason: "Granting full access poses a significant security risk.",
|
priorityReason: 'Granting full access poses a significant security risk.',
|
||||||
awsService: "IAM",
|
awsService: 'IAM',
|
||||||
awsServiceCategory: "Access Management",
|
awsServiceCategory: 'Access Management',
|
||||||
bestPracticeCategory: "Security",
|
bestPracticeCategory: 'Security',
|
||||||
requiredParametersForFix: [
|
requiredParametersForFix: [
|
||||||
{
|
{
|
||||||
name: "policy-revision-strategy",
|
name: 'policy-revision-strategy',
|
||||||
description: "Strategy to revise policies (e.g., remove, restrict actions)",
|
description: 'Strategy to revise policies (e.g., remove, restrict actions)',
|
||||||
default: "remove",
|
default: 'remove',
|
||||||
example: "remove",
|
example: 'remove'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: true,
|
isFixFunctionUsesDestructiveCommand: true,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{ name: "ListPoliciesCommand", reason: "Fetch all customer-managed policies." },
|
{ name: 'ListPoliciesCommand', reason: 'Fetch all customer-managed policies.' },
|
||||||
{ name: "GetPolicyVersionCommand", reason: "Retrieve the default version of the policy." },
|
{ name: 'GetPolicyVersionCommand', reason: 'Retrieve the default version of the policy.' }
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{ name: "CreatePolicyVersionCommand", reason: "Create a new policy version." },
|
{ name: 'CreatePolicyVersionCommand', reason: 'Create a new policy version.' },
|
||||||
{ name: "DeletePolicyVersionCommand", reason: "Delete outdated policy versions." },
|
{ name: 'DeletePolicyVersionCommand', reason: 'Delete outdated policy versions.' }
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: "Ensure revised policies meet the organization's security and access requirements."
|
||||||
"Ensure revised policies meet the organization's security and access requirements.",
|
})
|
||||||
});
|
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = "LOADED";
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = "CHECKING";
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl().then(
|
||||||
.then(
|
() => {
|
||||||
() => {
|
this.stats.status = 'FINISHED'
|
||||||
this.stats.status = "FINISHED";
|
},
|
||||||
},
|
(err) => {
|
||||||
(err) => {
|
this.stats.status = 'ERROR'
|
||||||
this.stats.status = "ERROR";
|
this.stats.errorMessage.push({
|
||||||
this.stats.errorMessage.push({
|
date: new Date(),
|
||||||
date: new Date(),
|
message: err.message
|
||||||
message: err.message,
|
})
|
||||||
});
|
}
|
||||||
}
|
)
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
|
|
||||||
const policiesResponse = await this.memoClient.send(
|
const policiesResponse = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }))
|
||||||
new ListPoliciesCommand({ Scope: "Local" })
|
const policies = policiesResponse.Policies || []
|
||||||
);
|
|
||||||
const policies = policiesResponse.Policies || [];
|
|
||||||
|
|
||||||
for (const policy of policies) {
|
for (const policy of policies) {
|
||||||
const policyVersionResponse = await this.memoClient.send(
|
const policyVersionResponse = await this.memoClient.send(
|
||||||
new GetPolicyVersionCommand({
|
new GetPolicyVersionCommand({
|
||||||
PolicyArn: policy.Arn!,
|
PolicyArn: policy.Arn!,
|
||||||
VersionId: policy.DefaultVersionId!,
|
VersionId: policy.DefaultVersionId!
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
const policyDocument = JSON.parse(
|
const policyDocument = JSON.parse(decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string))
|
||||||
decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string)
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasFullAccess = policyDocument.Statement.some((statement: any) => {
|
const hasFullAccess = policyDocument.Statement.some((statement: unknown) => {
|
||||||
if (statement.Effect === "Deny") return false;
|
if (statement.Effect === 'Deny') return false
|
||||||
const actions = Array.isArray(statement.Action)
|
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
|
||||||
? statement.Action
|
return actions.some((action: string) => action.endsWith(':*'))
|
||||||
: [statement.Action];
|
})
|
||||||
return actions.some((action: string) => action.endsWith(":*"));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hasFullAccess) {
|
if (hasFullAccess) {
|
||||||
nonCompliantResources.push(policy.Arn!);
|
nonCompliantResources.push(policy.Arn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(policy.Arn!);
|
compliantResources.push(policy.Arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix).then(
|
||||||
.then(
|
() => {
|
||||||
() => {
|
this.stats.status = 'FINISHED'
|
||||||
this.stats.status = "FINISHED";
|
},
|
||||||
},
|
(err) => {
|
||||||
(err) => {
|
this.stats.status = 'ERROR'
|
||||||
this.stats.status = "ERROR";
|
this.stats.errorMessage.push({
|
||||||
this.stats.errorMessage.push({
|
date: new Date(),
|
||||||
date: new Date(),
|
message: err.message
|
||||||
message: err.message,
|
})
|
||||||
});
|
}
|
||||||
}
|
)
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const strategy = requiredParametersForFix.find(
|
const strategy = requiredParametersForFix.find((param) => param.name === 'policy-revision-strategy')?.value
|
||||||
(param) => param.name === "policy-revision-strategy"
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!strategy) {
|
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) {
|
for (const policyArn of nonCompliantResources) {
|
||||||
const policyVersionResponse = await this.memoClient.send(
|
const policyVersionResponse = await this.memoClient.send(
|
||||||
new GetPolicyVersionCommand({
|
new GetPolicyVersionCommand({
|
||||||
PolicyArn: policyArn,
|
PolicyArn: policyArn,
|
||||||
VersionId: "v1",
|
VersionId: 'v1'
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
const policyDocument = JSON.parse(
|
const policyDocument = JSON.parse(decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string))
|
||||||
decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string)
|
|
||||||
);
|
|
||||||
|
|
||||||
policyDocument.Statement = policyDocument.Statement.filter((statement: any) => {
|
policyDocument.Statement = policyDocument.Statement.filter((statement: unknown) => {
|
||||||
if (statement.Effect === "Deny") return true;
|
if (statement.Effect === 'Deny') return true
|
||||||
const actions = Array.isArray(statement.Action)
|
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
|
||||||
? statement.Action
|
return !actions.some((action: string) => action.endsWith(':*'))
|
||||||
: [statement.Action];
|
})
|
||||||
return !actions.some((action: string) => action.endsWith(":*"));
|
|
||||||
});
|
|
||||||
|
|
||||||
const createVersionResponse = await this.client.send(
|
const createVersionResponse = await this.client.send(
|
||||||
new CreatePolicyVersionCommand({
|
new CreatePolicyVersionCommand({
|
||||||
PolicyArn: policyArn,
|
PolicyArn: policyArn,
|
||||||
PolicyDocument: JSON.stringify(policyDocument),
|
PolicyDocument: JSON.stringify(policyDocument),
|
||||||
SetAsDefault: true,
|
SetAsDefault: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
if (createVersionResponse.PolicyVersion?.VersionId) {
|
if (createVersionResponse.PolicyVersion?.VersionId) {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new DeletePolicyVersionCommand({
|
new DeletePolicyVersionCommand({
|
||||||
PolicyArn: policyArn,
|
PolicyArn: policyArn,
|
||||||
VersionId: policyVersionResponse.PolicyVersion!.VersionId,
|
VersionId: policyVersionResponse.PolicyVersion!.VersionId
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
import {
|
import { IAMClient, ListPoliciesCommand, ListEntitiesForPolicyCommand } from '@aws-sdk/client-iam'
|
||||||
IAMClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ListPoliciesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ListEntitiesForPolicyCommand,
|
|
||||||
} from '@aws-sdk/client-iam';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class IAMRoleManagedPolicyCheck implements BPSet {
|
export class IAMRoleManagedPolicyCheck implements BPSet {
|
||||||
private readonly client = new IAMClient({});
|
private readonly client = new IAMClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'IAMRoleManagedPolicyCheck',
|
name: 'IAMRoleManagedPolicyCheck',
|
||||||
description: 'Checks whether managed IAM policies are attached to any entities (roles, users, or groups).',
|
description: 'Checks whether managed IAM policies are attached to any entities (roles, users, or groups).',
|
||||||
priority: 3,
|
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',
|
awsService: 'IAM',
|
||||||
awsServiceCategory: 'Access Management',
|
awsServiceCategory: 'Access Management',
|
||||||
bestPracticeCategory: 'Security',
|
bestPracticeCategory: 'Security',
|
||||||
@ -30,86 +27,77 @@ export class IAMRoleManagedPolicyCheck implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListPoliciesCommand',
|
name: 'ListPoliciesCommand',
|
||||||
reason: 'Retrieve all customer-managed IAM policies.',
|
reason: 'Retrieve all customer-managed IAM policies.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ListEntitiesForPolicyCommand',
|
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: [],
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl().then(
|
||||||
.then(
|
() => {
|
||||||
() => {
|
this.stats.status = 'FINISHED'
|
||||||
this.stats.status = 'FINISHED';
|
},
|
||||||
},
|
(err) => {
|
||||||
(err) => {
|
this.stats.status = 'ERROR'
|
||||||
this.stats.status = 'ERROR';
|
this.stats.errorMessage.push({
|
||||||
this.stats.errorMessage.push({
|
date: new Date(),
|
||||||
date: new Date(),
|
message: err.message
|
||||||
message: err.message,
|
})
|
||||||
});
|
}
|
||||||
}
|
)
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const policies = await this.getPolicies();
|
const policies = await this.getPolicies()
|
||||||
|
|
||||||
for (const policy of policies) {
|
for (const policy of policies) {
|
||||||
const { attached } = await this.checkEntitiesForPolicy(policy.Arn!);
|
const { attached } = await this.checkEntitiesForPolicy(policy.Arn!)
|
||||||
|
|
||||||
if (attached) {
|
if (attached) {
|
||||||
compliantResources.push(policy.Arn!);
|
compliantResources.push(policy.Arn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(policy.Arn!);
|
nonCompliantResources.push(policy.Arn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async () => {
|
public readonly fix = async () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Fixing orphaned managed policies requires manual review and removal. Ensure these policies are no longer needed.'
|
'Fixing orphaned managed policies requires manual review and removal. Ensure these policies are no longer needed.'
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getPolicies = async () => {
|
private readonly getPolicies = async () => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' }))
|
||||||
new ListPoliciesCommand({ Scope: 'Local' })
|
return response.Policies || []
|
||||||
);
|
}
|
||||||
return response.Policies || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly checkEntitiesForPolicy = async (policyArn: string) => {
|
private readonly checkEntitiesForPolicy = async (policyArn: string) => {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new ListEntitiesForPolicyCommand({ PolicyArn: policyArn }))
|
||||||
new ListEntitiesForPolicyCommand({ PolicyArn: policyArn })
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
attached: Boolean(
|
attached: Boolean(response.PolicyGroups?.length || response.PolicyUsers?.length || response.PolicyRoles?.length)
|
||||||
response.PolicyGroups?.length ||
|
}
|
||||||
response.PolicyUsers?.length ||
|
}
|
||||||
response.PolicyRoles?.length
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda';
|
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class LambdaDLQCheck implements BPSet {
|
export class LambdaDLQCheck implements BPSet {
|
||||||
private readonly client = new LambdaClient({});
|
private readonly client = new LambdaClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'LambdaDLQCheck',
|
name: 'LambdaDLQCheck',
|
||||||
@ -26,111 +26,109 @@ export class LambdaDLQCheck implements BPSet {
|
|||||||
name: 'dlq-arn',
|
name: 'dlq-arn',
|
||||||
description: 'The ARN of the Dead Letter Queue to associate with the Lambda function.',
|
description: 'The ARN of the Dead Letter Queue to associate with the Lambda function.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'arn:aws:sqs:us-east-1:123456789012:example-queue',
|
example: 'arn:aws:sqs:us-east-1:123456789012:example-queue'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListFunctionsCommand',
|
name: 'ListFunctionsCommand',
|
||||||
reason: 'Retrieve all Lambda functions in the account.',
|
reason: 'Retrieve all Lambda functions in the account.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateFunctionConfigurationCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl().then(
|
||||||
.then(
|
() => {
|
||||||
() => {
|
this.stats.status = 'FINISHED'
|
||||||
this.stats.status = 'FINISHED';
|
},
|
||||||
},
|
(err) => {
|
||||||
(err) => {
|
this.stats.status = 'ERROR'
|
||||||
this.stats.status = 'ERROR';
|
this.stats.errorMessage.push({
|
||||||
this.stats.errorMessage.push({
|
date: new Date(),
|
||||||
date: new Date(),
|
message: err.message
|
||||||
message: err.message,
|
})
|
||||||
});
|
}
|
||||||
}
|
)
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const functions = await this.getFunctions();
|
const functions = await this.getFunctions()
|
||||||
|
|
||||||
for (const func of functions) {
|
for (const func of functions) {
|
||||||
if (func.DeadLetterConfig) {
|
if (func.DeadLetterConfig) {
|
||||||
compliantResources.push(func.FunctionArn!);
|
compliantResources.push(func.FunctionArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(func.FunctionArn!);
|
nonCompliantResources.push(func.FunctionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix).then(
|
||||||
.then(
|
() => {
|
||||||
() => {
|
this.stats.status = 'FINISHED'
|
||||||
this.stats.status = 'FINISHED';
|
},
|
||||||
},
|
(err) => {
|
||||||
(err) => {
|
this.stats.status = 'ERROR'
|
||||||
this.stats.status = 'ERROR';
|
this.stats.errorMessage.push({
|
||||||
this.stats.errorMessage.push({
|
date: new Date(),
|
||||||
date: new Date(),
|
message: err.message
|
||||||
message: err.message,
|
})
|
||||||
});
|
}
|
||||||
}
|
)
|
||||||
);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: 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) {
|
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) {
|
for (const functionArn of nonCompliantResources) {
|
||||||
const functionName = functionArn.split(':').pop()!;
|
const functionName = functionArn.split(':').pop()!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateFunctionConfigurationCommand({
|
new UpdateFunctionConfigurationCommand({
|
||||||
FunctionName: functionName,
|
FunctionName: functionName,
|
||||||
DeadLetterConfig: { TargetArn: dlqArn },
|
DeadLetterConfig: { TargetArn: dlqArn }
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getFunctions = async () => {
|
private readonly getFunctions = async () => {
|
||||||
const response = await this.memoClient.send(new ListFunctionsCommand({}));
|
const response = await this.memoClient.send(new ListFunctionsCommand({}))
|
||||||
return response.Functions || [];
|
return response.Functions || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
import {
|
import { LambdaClient, ListFunctionsCommand, GetPolicyCommand, RemovePermissionCommand } from '@aws-sdk/client-lambda'
|
||||||
LambdaClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ListFunctionsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetPolicyCommand,
|
|
||||||
RemovePermissionCommand,
|
|
||||||
} from '@aws-sdk/client-lambda';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
||||||
private readonly client = new LambdaClient({});
|
private readonly client = new LambdaClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'LambdaFunctionPublicAccessProhibited',
|
name: 'LambdaFunctionPublicAccessProhibited',
|
||||||
@ -31,124 +26,121 @@ export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListFunctionsCommand',
|
name: 'ListFunctionsCommand',
|
||||||
reason: 'Retrieve all Lambda functions in the account.',
|
reason: 'Retrieve all Lambda functions in the account.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'GetPolicyCommand',
|
name: 'GetPolicyCommand',
|
||||||
reason: 'Fetch the resource-based policy of a Lambda function.',
|
reason: 'Fetch the resource-based policy of a Lambda function.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'RemovePermissionCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const functions = await this.getFunctions();
|
const functions = await this.getFunctions()
|
||||||
|
|
||||||
for (const func of functions) {
|
for (const func of functions) {
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: func.FunctionName! }));
|
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: func.FunctionName! }))
|
||||||
const policy = JSON.parse(response.Policy!);
|
const policy = JSON.parse(response.Policy!)
|
||||||
|
|
||||||
const hasPublicAccess = policy.Statement.some(
|
const hasPublicAccess = policy.Statement.some(
|
||||||
(statement: any) => statement.Principal === '*' || statement.Principal?.AWS === '*'
|
(statement: unknown) => statement.Principal === '*' || statement.Principal?.AWS === '*'
|
||||||
);
|
)
|
||||||
|
|
||||||
if (hasPublicAccess) {
|
if (hasPublicAccess) {
|
||||||
nonCompliantResources.push(func.FunctionArn!);
|
nonCompliantResources.push(func.FunctionArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(func.FunctionArn!);
|
compliantResources.push(func.FunctionArn!)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as any).name === 'ResourceNotFoundException') {
|
if ((error as unknown).name === 'ResourceNotFoundException') {
|
||||||
compliantResources.push(func.FunctionArn!);
|
compliantResources.push(func.FunctionArn!)
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
await this.fixImpl(nonCompliantResources)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
for (const functionArn of nonCompliantResources) {
|
for (const functionArn of nonCompliantResources) {
|
||||||
const functionName = functionArn.split(':').pop()!;
|
const functionName = functionArn.split(':').pop()!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: functionName }));
|
const response = await this.memoClient.send(new GetPolicyCommand({ FunctionName: functionName }))
|
||||||
const policy = JSON.parse(response.Policy!);
|
const policy = JSON.parse(response.Policy!)
|
||||||
|
|
||||||
for (const statement of policy.Statement) {
|
for (const statement of policy.Statement) {
|
||||||
if (statement.Principal === '*' || statement.Principal?.AWS === '*') {
|
if (statement.Principal === '*' || statement.Principal?.AWS === '*') {
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new RemovePermissionCommand({
|
new RemovePermissionCommand({
|
||||||
FunctionName: functionName,
|
FunctionName: functionName,
|
||||||
StatementId: statement.Sid, // Use the actual StatementId from the policy
|
StatementId: statement.Sid // Use the actual StatementId from the policy
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as any).name !== 'ResourceNotFoundException') {
|
if ((error as unknown).name !== 'ResourceNotFoundException') {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getFunctions = async () => {
|
private readonly getFunctions = async () => {
|
||||||
const response = await this.memoClient.send(new ListFunctionsCommand({}));
|
const response = await this.memoClient.send(new ListFunctionsCommand({}))
|
||||||
return response.Functions || [];
|
return response.Functions || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda'
|
||||||
LambdaClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ListFunctionsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
UpdateFunctionConfigurationCommand
|
|
||||||
} from '@aws-sdk/client-lambda';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class LambdaFunctionSettingsCheck implements BPSet {
|
export class LambdaFunctionSettingsCheck implements BPSet {
|
||||||
private readonly client = new LambdaClient({});
|
private readonly client = new LambdaClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'LambdaFunctionSettingsCheck',
|
name: 'LambdaFunctionSettingsCheck',
|
||||||
@ -30,75 +26,76 @@ export class LambdaFunctionSettingsCheck implements BPSet {
|
|||||||
name: 'timeout',
|
name: 'timeout',
|
||||||
description: 'Timeout value in seconds for the Lambda function.',
|
description: 'Timeout value in seconds for the Lambda function.',
|
||||||
default: '3',
|
default: '3',
|
||||||
example: '30',
|
example: '30'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'memory-size',
|
name: 'memory-size',
|
||||||
description: 'Memory size in MB for the Lambda function.',
|
description: 'Memory size in MB for the Lambda function.',
|
||||||
default: '128',
|
default: '128',
|
||||||
example: '256',
|
example: '256'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListFunctionsCommand',
|
name: 'ListFunctionsCommand',
|
||||||
reason: 'Retrieve all Lambda functions in the account.',
|
reason: 'Retrieve all Lambda functions in the account.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateFunctionConfigurationCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const defaultTimeout = 3;
|
const defaultTimeout = 3
|
||||||
const defaultMemorySize = 128;
|
const defaultMemorySize = 128
|
||||||
|
|
||||||
const functions = await this.getFunctions();
|
const functions = await this.getFunctions()
|
||||||
|
|
||||||
for (const func of functions) {
|
for (const func of functions) {
|
||||||
if (func.Timeout === defaultTimeout || func.MemorySize === defaultMemorySize) {
|
if (func.Timeout === defaultTimeout || func.MemorySize === defaultMemorySize) {
|
||||||
nonCompliantResources.push(func.FunctionArn!);
|
nonCompliantResources.push(func.FunctionArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(func.FunctionArn!);
|
compliantResources.push(func.FunctionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
@ -106,42 +103,42 @@ export class LambdaFunctionSettingsCheck implements BPSet {
|
|||||||
) => {
|
) => {
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const timeout = requiredParametersForFix.find(param => param.name === 'timeout')?.value;
|
const timeout = requiredParametersForFix.find((param) => param.name === 'timeout')?.value
|
||||||
const memorySize = requiredParametersForFix.find(param => param.name === 'memory-size')?.value;
|
const memorySize = requiredParametersForFix.find((param) => param.name === 'memory-size')?.value
|
||||||
|
|
||||||
if (!timeout || !memorySize) {
|
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) {
|
for (const functionArn of nonCompliantResources) {
|
||||||
const functionName = functionArn.split(':').pop()!;
|
const functionName = functionArn.split(':').pop()!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateFunctionConfigurationCommand({
|
new UpdateFunctionConfigurationCommand({
|
||||||
FunctionName: functionName,
|
FunctionName: functionName,
|
||||||
Timeout: parseInt(timeout, 10),
|
Timeout: parseInt(timeout, 10),
|
||||||
MemorySize: parseInt(memorySize, 10),
|
MemorySize: parseInt(memorySize, 10)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getFunctions = async () => {
|
private readonly getFunctions = async () => {
|
||||||
const response = await this.memoClient.send(new ListFunctionsCommand({}));
|
const response = await this.memoClient.send(new ListFunctionsCommand({}))
|
||||||
return response.Functions || [];
|
return response.Functions || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { LambdaClient, ListFunctionsCommand, UpdateFunctionConfigurationCommand } from '@aws-sdk/client-lambda'
|
||||||
LambdaClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ListFunctionsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
UpdateFunctionConfigurationCommand
|
|
||||||
} from '@aws-sdk/client-lambda';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class LambdaInsideVPC implements BPSet {
|
export class LambdaInsideVPC implements BPSet {
|
||||||
private readonly client = new LambdaClient({});
|
private readonly client = new LambdaClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'LambdaInsideVPC',
|
name: 'LambdaInsideVPC',
|
||||||
@ -30,72 +26,73 @@ export class LambdaInsideVPC implements BPSet {
|
|||||||
name: 'subnet-ids',
|
name: 'subnet-ids',
|
||||||
description: 'Comma-separated list of VPC subnet IDs.',
|
description: 'Comma-separated list of VPC subnet IDs.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'subnet-abc123,subnet-def456',
|
example: 'subnet-abc123,subnet-def456'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'security-group-ids',
|
name: 'security-group-ids',
|
||||||
description: 'Comma-separated list of VPC security group IDs.',
|
description: 'Comma-separated list of VPC security group IDs.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'sg-abc123,sg-def456',
|
example: 'sg-abc123,sg-def456'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListFunctionsCommand',
|
name: 'ListFunctionsCommand',
|
||||||
reason: 'Retrieve all Lambda functions in the account.',
|
reason: 'Retrieve all Lambda functions in the account.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'UpdateFunctionConfigurationCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const functions = await this.getFunctions();
|
const functions = await this.getFunctions()
|
||||||
|
|
||||||
for (const func of functions) {
|
for (const func of functions) {
|
||||||
if (func.VpcConfig && Object.keys(func.VpcConfig).length > 0) {
|
if (func.VpcConfig && Object.keys(func.VpcConfig).length > 0) {
|
||||||
compliantResources.push(func.FunctionArn!);
|
compliantResources.push(func.FunctionArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(func.FunctionArn!);
|
nonCompliantResources.push(func.FunctionArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
@ -103,30 +100,30 @@ export class LambdaInsideVPC implements BPSet {
|
|||||||
) => {
|
) => {
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const subnetIds = requiredParametersForFix.find(param => param.name === 'subnet-ids')?.value;
|
const subnetIds = requiredParametersForFix.find((param) => param.name === 'subnet-ids')?.value
|
||||||
const securityGroupIds = requiredParametersForFix.find(param => param.name === 'security-group-ids')?.value;
|
const securityGroupIds = requiredParametersForFix.find((param) => param.name === 'security-group-ids')?.value
|
||||||
|
|
||||||
if (!subnetIds || !securityGroupIds) {
|
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) {
|
for (const functionArn of nonCompliantResources) {
|
||||||
const functionName = functionArn.split(':').pop()!;
|
const functionName = functionArn.split(':').pop()!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new UpdateFunctionConfigurationCommand({
|
new UpdateFunctionConfigurationCommand({
|
||||||
FunctionName: functionName,
|
FunctionName: functionName,
|
||||||
@ -135,12 +132,12 @@ export class LambdaInsideVPC implements BPSet {
|
|||||||
SecurityGroupIds: securityGroupIds.split(',')
|
SecurityGroupIds: securityGroupIds.split(',')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getFunctions = async () => {
|
private readonly getFunctions = async () => {
|
||||||
const response = await this.memoClient.send(new ListFunctionsCommand({}));
|
const response = await this.memoClient.send(new ListFunctionsCommand({}))
|
||||||
return response.Functions || [];
|
return response.Functions || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,19 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BackupClient, ListRecoveryPointsByResourceCommand } from '@aws-sdk/client-backup'
|
||||||
DescribeDBClustersCommand,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ModifyDBClusterCommand
|
import { Memorizer } from '../../Memorizer'
|
||||||
} 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 {
|
export class AuroraLastBackupRecoveryPointCreated implements BPSet {
|
||||||
private readonly rdsClient = new RDSClient({});
|
private readonly rdsClient = new RDSClient({})
|
||||||
private readonly backupClient = new BackupClient({});
|
private readonly backupClient = new BackupClient({})
|
||||||
private readonly memoRdsClient = Memorizer.memo(this.rdsClient);
|
private readonly memoRdsClient = Memorizer.memo(this.rdsClient)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'AuroraLastBackupRecoveryPointCreated',
|
name: 'AuroraLastBackupRecoveryPointCreated',
|
||||||
@ -35,77 +28,75 @@ export class AuroraLastBackupRecoveryPointCreated implements BPSet {
|
|||||||
name: 'backup-retention-period',
|
name: 'backup-retention-period',
|
||||||
description: 'The number of days to retain backups for the DB cluster.',
|
description: 'The number of days to retain backups for the DB cluster.',
|
||||||
default: '7',
|
default: '7',
|
||||||
example: '7',
|
example: '7'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeDBClustersCommand',
|
name: 'DescribeDBClustersCommand',
|
||||||
reason: 'Retrieve information about Aurora DB clusters.',
|
reason: 'Retrieve information about Aurora DB clusters.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ListRecoveryPointsByResourceCommand',
|
name: 'ListRecoveryPointsByResourceCommand',
|
||||||
reason: 'Check the recovery points associated with the DB cluster.',
|
reason: 'Check the recovery points associated with the DB cluster.'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyDBClusterCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
const recoveryPoints = await this.getRecoveryPoints(cluster.DBClusterArn!);
|
const recoveryPoints = await this.getRecoveryPoints(cluster.DBClusterArn!)
|
||||||
const recoveryDates = recoveryPoints.map(rp => new Date(rp.CreationDate!));
|
const recoveryDates = recoveryPoints.map((rp) => new Date(rp.CreationDate!))
|
||||||
recoveryDates.sort((a, b) => b.getTime() - a.getTime());
|
recoveryDates.sort((a, b) => b.getTime() - a.getTime())
|
||||||
|
|
||||||
if (
|
if (recoveryDates.length > 0 && new Date().getTime() - recoveryDates[0].getTime() < 24 * 60 * 60 * 1000) {
|
||||||
recoveryDates.length > 0 &&
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
new Date().getTime() - recoveryDates[0].getTime() < 24 * 60 * 60 * 1000
|
|
||||||
) {
|
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
@ -113,50 +104,46 @@ export class AuroraLastBackupRecoveryPointCreated implements BPSet {
|
|||||||
) => {
|
) => {
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const retentionPeriod = requiredParametersForFix.find(
|
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'backup-retention-period')?.value
|
||||||
param => param.name === 'backup-retention-period'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!retentionPeriod) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.rdsClient.send(
|
await this.rdsClient.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
|
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getRecoveryPoints = async (resourceArn: string) => {
|
private readonly getRecoveryPoints = async (resourceArn: string) => {
|
||||||
const response = await this.backupClient.send(
|
const response = await this.backupClient.send(new ListRecoveryPointsByResourceCommand({ ResourceArn: resourceArn }))
|
||||||
new ListRecoveryPointsByResourceCommand({ ResourceArn: resourceArn })
|
return response.RecoveryPoints || []
|
||||||
);
|
}
|
||||||
return response.RecoveryPoints || [];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBClusterCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class AuroraMySQLBacktrackingEnabled implements BPSet {
|
export class AuroraMySQLBacktrackingEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'AuroraMySQLBacktrackingEnabled',
|
name: 'AuroraMySQLBacktrackingEnabled',
|
||||||
@ -30,94 +26,91 @@ export class AuroraMySQLBacktrackingEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'DescribeDBClustersCommand',
|
name: 'DescribeDBClustersCommand',
|
||||||
reason: 'Fetch Aurora MySQL DB clusters and check backtracking configuration.',
|
reason: 'Fetch Aurora MySQL DB clusters and check backtracking configuration.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'ModifyDBClusterCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
if (
|
if (
|
||||||
cluster.Engine === 'aurora-mysql' &&
|
cluster.Engine === 'aurora-mysql' &&
|
||||||
(!cluster.EarliestBacktrackTime || cluster.EarliestBacktrackTime === null)
|
(!cluster.EarliestBacktrackTime || cluster.EarliestBacktrackTime === null)
|
||||||
) {
|
) {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
await this.fixImpl(nonCompliantResources)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
BacktrackWindow: 3600, // Set backtracking window to 1 hour
|
BacktrackWindow: 3600 // Set backtracking window to 1 hour
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBInstancesCommand, ModifyDBInstanceCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBInstancesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBInstanceCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class DBInstanceBackupEnabled implements BPSet {
|
export class DBInstanceBackupEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'DBInstanceBackupEnabled',
|
name: 'DBInstanceBackupEnabled',
|
||||||
@ -47,49 +43,49 @@ export class DBInstanceBackupEnabled implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure the retention period aligns with your organization’s backup policy.'
|
adviseBeforeFixFunction: 'Ensure the retention period aligns with your organization’s backup policy.'
|
||||||
});
|
})
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbInstances = await this.getDBInstances();
|
const dbInstances = await this.getDBInstances()
|
||||||
|
|
||||||
for (const instance of dbInstances) {
|
for (const instance of dbInstances) {
|
||||||
if (instance.BackupRetentionPeriod && instance.BackupRetentionPeriod > 0) {
|
if (instance.BackupRetentionPeriod && instance.BackupRetentionPeriod > 0) {
|
||||||
compliantResources.push(instance.DBInstanceArn!);
|
compliantResources.push(instance.DBInstanceArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(instance.DBInstanceArn!);
|
nonCompliantResources.push(instance.DBInstanceArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
@ -97,42 +93,40 @@ export class DBInstanceBackupEnabled implements BPSet {
|
|||||||
) => {
|
) => {
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const retentionPeriod = requiredParametersForFix.find(
|
const retentionPeriod = requiredParametersForFix.find((param) => param.name === 'retention-period')?.value
|
||||||
(param) => param.name === 'retention-period'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!retentionPeriod) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const instanceId = arn.split(':instance/')[1];
|
const instanceId = arn.split(':instance/')[1]
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBInstanceCommand({
|
new ModifyDBInstanceCommand({
|
||||||
DBInstanceIdentifier: instanceId,
|
DBInstanceIdentifier: instanceId,
|
||||||
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
|
BackupRetentionPeriod: parseInt(retentionPeriod, 10)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBInstances = async () => {
|
private readonly getDBInstances = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
|
||||||
return response.DBInstances || [];
|
return response.DBInstances || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBClusterCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSClusterAutoMinorVersionUpgradeEnabled implements BPSet {
|
export class RDSClusterAutoMinorVersionUpgradeEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSClusterAutoMinorVersionUpgradeEnabled',
|
name: 'RDSClusterAutoMinorVersionUpgradeEnabled',
|
||||||
description: 'Ensures Auto Minor Version Upgrade is enabled for RDS clusters.',
|
description: 'Ensures Auto Minor Version Upgrade is enabled for RDS clusters.',
|
||||||
priority: 1,
|
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',
|
awsService: 'RDS',
|
||||||
awsServiceCategory: 'Database',
|
awsServiceCategory: 'Database',
|
||||||
bestPracticeCategory: 'Configuration Management',
|
bestPracticeCategory: 'Configuration Management',
|
||||||
@ -39,83 +36,81 @@ export class RDSClusterAutoMinorVersionUpgradeEnabled implements BPSet {
|
|||||||
reason: 'Enable auto minor version upgrade for non-compliant RDS clusters.'
|
reason: 'Enable auto minor version upgrade for non-compliant RDS clusters.'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure that enabling auto minor version upgrades aligns with your organization’s change management policies.'
|
adviseBeforeFixFunction:
|
||||||
});
|
'Ensure that enabling auto minor version upgrades aligns with your organization’s change management policies.'
|
||||||
|
})
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
if (cluster.Engine === 'docdb' || cluster.AutoMinorVersionUpgrade) {
|
if (cluster.Engine === 'docdb' || cluster.AutoMinorVersionUpgrade) {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
await this.fixImpl(nonCompliantResources)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
AutoMinorVersionUpgrade: true
|
AutoMinorVersionUpgrade: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBClusterCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSClusterDefaultAdminCheck implements BPSet {
|
export class RDSClusterDefaultAdminCheck implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSClusterDefaultAdminCheck',
|
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.'
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
if (!['admin', 'postgres'].includes(cluster.MasterUsername!)) {
|
if (!['admin', 'postgres'].includes(cluster.MasterUsername!)) {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
@ -103,46 +99,42 @@ export class RDSClusterDefaultAdminCheck implements BPSet {
|
|||||||
) => {
|
) => {
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const newMasterUsername = requiredParametersForFix.find(
|
const newMasterUsername = requiredParametersForFix.find((param) => param.name === 'new-master-username')?.value
|
||||||
(param) => param.name === 'new-master-username'
|
const newMasterPassword = requiredParametersForFix.find((param) => param.name === 'new-master-password')?.value
|
||||||
)?.value;
|
|
||||||
const newMasterPassword = requiredParametersForFix.find(
|
|
||||||
(param) => param.name === 'new-master-password'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!newMasterUsername || !newMasterPassword) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
MasterUserPassword: newMasterPassword
|
MasterUserPassword: newMasterPassword
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBClusterCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSClusterDeletionProtectionEnabled implements BPSet {
|
export class RDSClusterDeletionProtectionEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSClusterDeletionProtectionEnabled',
|
name: 'RDSClusterDeletionProtectionEnabled',
|
||||||
@ -40,82 +36,79 @@ export class RDSClusterDeletionProtectionEnabled implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure that enabling deletion protection aligns with your operational policies.'
|
adviseBeforeFixFunction: 'Ensure that enabling deletion protection aligns with your operational policies.'
|
||||||
});
|
})
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
if (cluster.DeletionProtection) {
|
if (cluster.DeletionProtection) {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
await this.fixImpl(nonCompliantResources)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
DeletionProtection: true
|
DeletionProtection: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand
|
import { Memorizer } from '../../Memorizer'
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSClusterEncryptedAtRest implements BPSet {
|
export class RDSClusterEncryptedAtRest implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSClusterEncryptedAtRest',
|
name: 'RDSClusterEncryptedAtRest',
|
||||||
@ -33,67 +30,66 @@ export class RDSClusterEncryptedAtRest implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [],
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
if (cluster.StorageEncrypted) {
|
if (cluster.StorageEncrypted) {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async () => {
|
||||||
nonCompliantResources: string[],
|
this.stats.status = 'ERROR'
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'ERROR';
|
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
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(
|
throw new Error(
|
||||||
'Fixing encryption at rest requires recreating the cluster. Please manually recreate the cluster with encryption enabled.'
|
'Fixing encryption at rest requires recreating the cluster. Please manually recreate the cluster with encryption enabled.'
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBClusterCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSClusterIAMAuthenticationEnabled implements BPSet {
|
export class RDSClusterIAMAuthenticationEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSClusterIAMAuthenticationEnabled',
|
name: 'RDSClusterIAMAuthenticationEnabled',
|
||||||
@ -39,91 +35,86 @@ export class RDSClusterIAMAuthenticationEnabled implements BPSet {
|
|||||||
reason: 'To enable IAM Database Authentication for non-compliant clusters.'
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
if (
|
if (cluster.Engine === 'docdb' || cluster.IAMDatabaseAuthenticationEnabled) {
|
||||||
cluster.Engine === 'docdb' ||
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
cluster.IAMDatabaseAuthenticationEnabled
|
|
||||||
) {
|
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
EnableIAMDatabaseAuthentication: true
|
EnableIAMDatabaseAuthentication: true
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand
|
import { Memorizer } from '../../Memorizer'
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSClusterMultiAZEnabled implements BPSet {
|
export class RDSClusterMultiAZEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSClusterMultiAZEnabled',
|
name: 'RDSClusterMultiAZEnabled',
|
||||||
@ -42,80 +39,75 @@ export class RDSClusterMultiAZEnabled implements BPSet {
|
|||||||
commandUsedInFixFunction: [],
|
commandUsedInFixFunction: [],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction:
|
||||||
'Enabling Multi-AZ for an existing cluster may require significant reconfiguration and potential downtime. Proceed with caution.'
|
'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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
if ((cluster.AvailabilityZones || []).length > 1) {
|
if ((cluster.AvailabilityZones || []).length > 1) {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async () => {
|
||||||
nonCompliantResources: string[],
|
throw new Error('Enabling Multi-AZ requires cluster reconfiguration. This must be performed manually.')
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
}
|
||||||
) => {
|
|
||||||
throw new Error(
|
|
||||||
'Enabling Multi-AZ requires cluster reconfiguration. This must be performed manually.'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { EC2Client, DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2'
|
||||||
DescribeDBClustersCommand,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ModifyDBClusterCommand
|
import { Memorizer } from '../../Memorizer'
|
||||||
} 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 {
|
export class RDSDBSecurityGroupNotAllowed implements BPSet {
|
||||||
private readonly rdsClient = new RDSClient({});
|
private readonly rdsClient = new RDSClient({})
|
||||||
private readonly ec2Client = new EC2Client({});
|
private readonly ec2Client = new EC2Client({})
|
||||||
private readonly memoRdsClient = Memorizer.memo(this.rdsClient);
|
private readonly memoRdsClient = Memorizer.memo(this.rdsClient)
|
||||||
private readonly memoEc2Client = Memorizer.memo(this.ec2Client);
|
private readonly memoEc2Client = Memorizer.memo(this.ec2Client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSDBSecurityGroupNotAllowed',
|
name: 'RDSDBSecurityGroupNotAllowed',
|
||||||
@ -48,94 +44,91 @@ export class RDSDBSecurityGroupNotAllowed implements BPSet {
|
|||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction:
|
||||||
'Ensure valid non-default security groups are associated with the clusters before applying the fix.'
|
'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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
const defaultSecurityGroupIds = (await this.getDefaultSecurityGroups()).map(sg => sg.GroupId!);
|
const defaultSecurityGroupIds = (await this.getDefaultSecurityGroups()).map((sg) => sg.GroupId!)
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
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!))) {
|
if (activeSecurityGroups.some((sg) => defaultSecurityGroupIds.includes(sg.VpcSecurityGroupId!))) {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
this.stats.status = 'CHECKING'
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING';
|
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
|
|
||||||
await this.rdsClient.send(
|
await this.rdsClient.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
VpcSecurityGroupIds: [] // Ensure valid non-default security groups are used here
|
VpcSecurityGroupIds: [] // Ensure valid non-default security groups are used here
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDefaultSecurityGroups = async () => {
|
private readonly getDefaultSecurityGroups = async () => {
|
||||||
const response = await this.memoEc2Client.send(
|
const response = await this.memoEc2Client.send(
|
||||||
new DescribeSecurityGroupsCommand({ Filters: [{ Name: 'group-name', Values: ['default'] }] })
|
new DescribeSecurityGroupsCommand({ Filters: [{ Name: 'group-name', Values: ['default'] }] })
|
||||||
);
|
)
|
||||||
return response.SecurityGroups || [];
|
return response.SecurityGroups || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBInstancesCommand, ModifyDBInstanceCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBInstancesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBInstanceCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSEnhancedMonitoringEnabled implements BPSet {
|
export class RDSEnhancedMonitoringEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSEnhancedMonitoringEnabled',
|
name: 'RDSEnhancedMonitoringEnabled',
|
||||||
@ -47,95 +43,93 @@ export class RDSEnhancedMonitoringEnabled implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure that enabling Enhanced Monitoring does not conflict with existing configurations.'
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbInstances = await this.getDBInstances();
|
const dbInstances = await this.getDBInstances()
|
||||||
|
|
||||||
for (const instance of dbInstances) {
|
for (const instance of dbInstances) {
|
||||||
if (instance.MonitoringInterval && instance.MonitoringInterval > 0) {
|
if (instance.MonitoringInterval && instance.MonitoringInterval > 0) {
|
||||||
compliantResources.push(instance.DBInstanceArn!);
|
compliantResources.push(instance.DBInstanceArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(instance.DBInstanceArn!);
|
nonCompliantResources.push(instance.DBInstanceArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const monitoringInterval = requiredParametersForFix.find(
|
const monitoringInterval = requiredParametersForFix.find((param) => param.name === 'monitoring-interval')?.value
|
||||||
(param) => param.name === 'monitoring-interval'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!monitoringInterval) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const instanceId = arn.split(':instance/')[1];
|
const instanceId = arn.split(':instance/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBInstanceCommand({
|
new ModifyDBInstanceCommand({
|
||||||
DBInstanceIdentifier: instanceId,
|
DBInstanceIdentifier: instanceId,
|
||||||
MonitoringInterval: parseInt(monitoringInterval, 10)
|
MonitoringInterval: parseInt(monitoringInterval, 10)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBInstances = async () => {
|
private readonly getDBInstances = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
|
||||||
return response.DBInstances || [];
|
return response.DBInstances || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBInstancesCommand, ModifyDBInstanceCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBInstancesCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBInstanceCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSInstancePublicAccessCheck implements BPSet {
|
export class RDSInstancePublicAccessCheck implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSInstancePublicAccessCheck',
|
name: 'RDSInstancePublicAccessCheck',
|
||||||
@ -39,88 +35,86 @@ export class RDSInstancePublicAccessCheck implements BPSet {
|
|||||||
reason: 'Disables public access for non-compliant RDS instances.'
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const dbInstances = await this.getDBInstances();
|
const dbInstances = await this.getDBInstances()
|
||||||
|
|
||||||
for (const instance of dbInstances) {
|
for (const instance of dbInstances) {
|
||||||
if (instance.PubliclyAccessible) {
|
if (instance.PubliclyAccessible) {
|
||||||
nonCompliantResources.push(instance.DBInstanceArn!);
|
nonCompliantResources.push(instance.DBInstanceArn!)
|
||||||
} else {
|
} else {
|
||||||
compliantResources.push(instance.DBInstanceArn!);
|
compliantResources.push(instance.DBInstanceArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const instanceId = arn.split(':instance/')[1];
|
const instanceId = arn.split(':instance/')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBInstanceCommand({
|
new ModifyDBInstanceCommand({
|
||||||
DBInstanceIdentifier: instanceId,
|
DBInstanceIdentifier: instanceId,
|
||||||
PubliclyAccessible: false
|
PubliclyAccessible: false
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBInstances = async () => {
|
private readonly getDBInstances = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}));
|
const response = await this.memoClient.send(new DescribeDBInstancesCommand({}))
|
||||||
return response.DBInstances || [];
|
return response.DBInstances || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClustersCommand, ModifyDBClusterCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClustersCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
ModifyDBClusterCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSLoggingEnabled implements BPSet {
|
export class RDSLoggingEnabled implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSLoggingEnabled',
|
name: 'RDSLoggingEnabled',
|
||||||
@ -40,111 +36,108 @@ export class RDSLoggingEnabled implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure that enabling logs does not negatively impact application performance.'
|
adviseBeforeFixFunction: 'Ensure that enabling logs does not negatively impact application performance.'
|
||||||
});
|
})
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const logsForEngine: Record<string, string[]> = {
|
const logsForEngine: Record<string, string[]> = {
|
||||||
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
|
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
|
||||||
'aurora-postgresql': ['postgresql'],
|
'aurora-postgresql': ['postgresql'],
|
||||||
'docdb': ['audit', 'profiler'],
|
docdb: ['audit', 'profiler'],
|
||||||
'mysql': ['audit', 'error', 'general', 'slowquery'],
|
mysql: ['audit', 'error', 'general', 'slowquery'],
|
||||||
'postgresql': ['postgresql', 'upgrade'],
|
postgresql: ['postgresql', 'upgrade']
|
||||||
};
|
}
|
||||||
|
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const cluster of dbClusters) {
|
for (const cluster of dbClusters) {
|
||||||
const requiredLogs = logsForEngine[cluster.Engine!] || [];
|
const requiredLogs = logsForEngine[cluster.Engine!] || []
|
||||||
const enabledLogs = cluster.EnabledCloudwatchLogsExports || [];
|
const enabledLogs = cluster.EnabledCloudwatchLogsExports || []
|
||||||
|
|
||||||
if (JSON.stringify(enabledLogs) === JSON.stringify(requiredLogs)) {
|
if (JSON.stringify(enabledLogs) === JSON.stringify(requiredLogs)) {
|
||||||
compliantResources.push(cluster.DBClusterArn!);
|
compliantResources.push(cluster.DBClusterArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(cluster.DBClusterArn!);
|
nonCompliantResources.push(cluster.DBClusterArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
const logsForEngine: Record<string, string[]> = {
|
const logsForEngine: Record<string, string[]> = {
|
||||||
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
|
'aurora-mysql': ['audit', 'error', 'general', 'slowquery'],
|
||||||
'aurora-postgresql': ['postgresql'],
|
'aurora-postgresql': ['postgresql'],
|
||||||
'docdb': ['audit', 'profiler']
|
docdb: ['audit', 'profiler']
|
||||||
};
|
}
|
||||||
|
|
||||||
const dbClusters = await this.getDBClusters();
|
const dbClusters = await this.getDBClusters()
|
||||||
|
|
||||||
for (const arn of nonCompliantResources) {
|
for (const arn of nonCompliantResources) {
|
||||||
const clusterId = arn.split(':cluster/')[1];
|
const clusterId = arn.split(':cluster/')[1]
|
||||||
const cluster = dbClusters.find((c) => c.DBClusterArn === arn);
|
const cluster = dbClusters.find((c) => c.DBClusterArn === arn)
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
const logsToEnable = logsForEngine[cluster.Engine!] || [];
|
const logsToEnable = logsForEngine[cluster.Engine!] || []
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new ModifyDBClusterCommand({
|
new ModifyDBClusterCommand({
|
||||||
DBClusterIdentifier: clusterId,
|
DBClusterIdentifier: clusterId,
|
||||||
CloudwatchLogsExportConfiguration: { EnableLogTypes: logsToEnable }
|
CloudwatchLogsExportConfiguration: { EnableLogTypes: logsToEnable }
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusters = async () => {
|
private readonly getDBClusters = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClustersCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClustersCommand({}))
|
||||||
return response.DBClusters || [];
|
return response.DBClusters || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
import {
|
import { RDSClient, DescribeDBClusterSnapshotsCommand, CopyDBClusterSnapshotCommand } from '@aws-sdk/client-rds'
|
||||||
RDSClient,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
DescribeDBClusterSnapshotsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
CopyDBClusterSnapshotCommand
|
|
||||||
} from '@aws-sdk/client-rds';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class RDSSnapshotEncrypted implements BPSet {
|
export class RDSSnapshotEncrypted implements BPSet {
|
||||||
private readonly client = new RDSClient({});
|
private readonly client = new RDSClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'RDSSnapshotEncrypted',
|
name: 'RDSSnapshotEncrypted',
|
||||||
@ -47,83 +43,81 @@ export class RDSSnapshotEncrypted implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure that the KMS key is properly configured and accessible.'
|
adviseBeforeFixFunction: 'Ensure that the KMS key is properly configured and accessible.'
|
||||||
});
|
})
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const snapshots = await this.getDBClusterSnapshots();
|
const snapshots = await this.getDBClusterSnapshots()
|
||||||
|
|
||||||
for (const snapshot of snapshots) {
|
for (const snapshot of snapshots) {
|
||||||
if (snapshot.StorageEncrypted) {
|
if (snapshot.StorageEncrypted) {
|
||||||
compliantResources.push(snapshot.DBClusterSnapshotArn!);
|
compliantResources.push(snapshot.DBClusterSnapshotArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(snapshot.DBClusterSnapshotArn!);
|
nonCompliantResources.push(snapshot.DBClusterSnapshotArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const kmsKeyId = requiredParametersForFix.find(
|
const kmsKeyId = requiredParametersForFix.find((param) => param.name === 'kms-key-id')?.value
|
||||||
(param) => param.name === 'kms-key-id'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!kmsKeyId) {
|
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) {
|
for (const arn of nonCompliantResources) {
|
||||||
const snapshotId = arn.split(':snapshot:')[1];
|
const snapshotId = arn.split(':snapshot:')[1]
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new CopyDBClusterSnapshotCommand({
|
new CopyDBClusterSnapshotCommand({
|
||||||
@ -131,12 +125,12 @@ export class RDSSnapshotEncrypted implements BPSet {
|
|||||||
TargetDBClusterSnapshotIdentifier: `${snapshotId}-encrypted`,
|
TargetDBClusterSnapshotIdentifier: `${snapshotId}-encrypted`,
|
||||||
KmsKeyId: kmsKeyId
|
KmsKeyId: kmsKeyId
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getDBClusterSnapshots = async () => {
|
private readonly getDBClusterSnapshots = async () => {
|
||||||
const response = await this.memoClient.send(new DescribeDBClusterSnapshotsCommand({}));
|
const response = await this.memoClient.send(new DescribeDBClusterSnapshotsCommand({}))
|
||||||
return response.DBClusterSnapshots || [];
|
return response.DBClusterSnapshots || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,22 +3,22 @@ import {
|
|||||||
ListAccessPointsCommand,
|
ListAccessPointsCommand,
|
||||||
DeleteAccessPointCommand,
|
DeleteAccessPointCommand,
|
||||||
CreateAccessPointCommand
|
CreateAccessPointCommand
|
||||||
} from '@aws-sdk/client-s3-control';
|
} from '@aws-sdk/client-s3-control'
|
||||||
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
|
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class S3AccessPointInVpcOnly implements BPSet {
|
export class S3AccessPointInVpcOnly implements BPSet {
|
||||||
private readonly client = new S3ControlClient({});
|
private readonly client = new S3ControlClient({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
private readonly stsClient = Memorizer.memo(new STSClient({}));
|
private readonly stsClient = Memorizer.memo(new STSClient({}))
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3AccessPointInVpcOnly',
|
name: 'S3AccessPointInVpcOnly',
|
||||||
@ -54,94 +54,92 @@ export class S3AccessPointInVpcOnly implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure that the specified VPC ID is correct and accessible.'
|
adviseBeforeFixFunction: 'Ensure that the specified VPC ID is correct and accessible.'
|
||||||
});
|
})
|
||||||
|
|
||||||
public readonly getStats = () => this.stats;
|
public readonly getStats = () => this.stats
|
||||||
|
|
||||||
public readonly clearStats = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const accountId = await this.getAccountId();
|
const accountId = await this.getAccountId()
|
||||||
|
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new ListAccessPointsCommand({ AccountId: accountId }))
|
||||||
new ListAccessPointsCommand({ AccountId: accountId })
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const accessPoint of response.AccessPointList || []) {
|
for (const accessPoint of response.AccessPointList || []) {
|
||||||
if (accessPoint.NetworkOrigin === 'VPC') {
|
if (accessPoint.NetworkOrigin === 'VPC') {
|
||||||
compliantResources.push(accessPoint.AccessPointArn!);
|
compliantResources.push(accessPoint.AccessPointArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(accessPoint.AccessPointArn!);
|
nonCompliantResources.push(accessPoint.AccessPointArn!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const accountId = await this.getAccountId();
|
const accountId = await this.getAccountId()
|
||||||
const vpcId = requiredParametersForFix.find(param => param.name === 'your-vpc-id')?.value;
|
const vpcId = requiredParametersForFix.find((param) => param.name === 'your-vpc-id')?.value
|
||||||
|
|
||||||
if (!vpcId) {
|
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) {
|
for (const accessPointArn of nonCompliantResources) {
|
||||||
const accessPointName = accessPointArn.split(':').pop()!;
|
const accessPointName = accessPointArn.split(':').pop()!
|
||||||
const bucketName = accessPointArn.split('/')[1]!;
|
const bucketName = accessPointArn.split('/')[1]!
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new DeleteAccessPointCommand({
|
new DeleteAccessPointCommand({
|
||||||
AccountId: accountId,
|
AccountId: accountId,
|
||||||
Name: accessPointName
|
Name: accessPointName
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new CreateAccessPointCommand({
|
new CreateAccessPointCommand({
|
||||||
@ -152,12 +150,12 @@ export class S3AccessPointInVpcOnly implements BPSet {
|
|||||||
VpcId: vpcId
|
VpcId: vpcId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getAccountId = async (): Promise<string> => {
|
private readonly getAccountId = async (): Promise<string> => {
|
||||||
const response = await this.stsClient.send(new GetCallerIdentityCommand({}));
|
const response = await this.stsClient.send(new GetCallerIdentityCommand({}))
|
||||||
return response.Account!;
|
return response.Account!
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,26 +3,27 @@ import {
|
|||||||
ListBucketsCommand,
|
ListBucketsCommand,
|
||||||
GetObjectLockConfigurationCommand,
|
GetObjectLockConfigurationCommand,
|
||||||
PutObjectLockConfigurationCommand
|
PutObjectLockConfigurationCommand
|
||||||
} from '@aws-sdk/client-s3';
|
} from '@aws-sdk/client-s3'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class S3BucketDefaultLockEnabled implements BPSet {
|
export class S3BucketDefaultLockEnabled implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3BucketDefaultLockEnabled',
|
name: 'S3BucketDefaultLockEnabled',
|
||||||
description: 'Ensures that all S3 buckets have default object lock configuration enabled.',
|
description: 'Ensures that all S3 buckets have default object lock configuration enabled.',
|
||||||
priority: 2,
|
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',
|
awsService: 'S3',
|
||||||
awsServiceCategory: 'Buckets',
|
awsServiceCategory: 'Buckets',
|
||||||
bestPracticeCategory: 'Data Protection',
|
bestPracticeCategory: 'Data Protection',
|
||||||
@ -40,83 +41,79 @@ export class S3BucketDefaultLockEnabled implements BPSet {
|
|||||||
reason: 'Enables object lock configuration with a default retention rule.'
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
try {
|
try {
|
||||||
await this.memoClient.send(
|
await this.memoClient.send(new GetObjectLockConfigurationCommand({ Bucket: bucket.Name! }))
|
||||||
new GetObjectLockConfigurationCommand({ Bucket: bucket.Name! })
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
);
|
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as any).name === 'ObjectLockConfigurationNotFoundError') {
|
if ((error as unknown).name === 'ObjectLockConfigurationNotFoundError') {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
for (const bucketArn of nonCompliantResources) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!;
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutObjectLockConfigurationCommand({
|
new PutObjectLockConfigurationCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
@ -130,12 +127,12 @@ export class S3BucketDefaultLockEnabled implements BPSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,20 @@ import {
|
|||||||
ListBucketsCommand,
|
ListBucketsCommand,
|
||||||
GetPublicAccessBlockCommand,
|
GetPublicAccessBlockCommand,
|
||||||
PutPublicAccessBlockCommand
|
PutPublicAccessBlockCommand
|
||||||
} from '@aws-sdk/client-s3';
|
} from '@aws-sdk/client-s3'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class S3BucketLevelPublicAccessProhibited implements BPSet {
|
export class S3BucketLevelPublicAccessProhibited implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: []
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3BucketLevelPublicAccessProhibited',
|
name: 'S3BucketLevelPublicAccessProhibited',
|
||||||
@ -41,88 +41,83 @@ export class S3BucketLevelPublicAccessProhibited implements BPSet {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction: 'Ensure no legitimate use cases require public access before applying this fix.'
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetPublicAccessBlockCommand({ Bucket: bucket.Name! }))
|
||||||
new GetPublicAccessBlockCommand({ Bucket: bucket.Name! })
|
const config = response.PublicAccessBlockConfiguration
|
||||||
);
|
|
||||||
const config = response.PublicAccessBlockConfiguration;
|
|
||||||
if (
|
if (
|
||||||
config?.BlockPublicAcls &&
|
config?.BlockPublicAcls &&
|
||||||
config?.IgnorePublicAcls &&
|
config?.IgnorePublicAcls &&
|
||||||
config?.BlockPublicPolicy &&
|
config?.BlockPublicPolicy &&
|
||||||
config?.RestrictPublicBuckets
|
config?.RestrictPublicBuckets
|
||||||
) {
|
) {
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
for (const bucketArn of nonCompliantResources) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!;
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutPublicAccessBlockCommand({
|
new PutPublicAccessBlockCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
@ -133,12 +128,12 @@ export class S3BucketLevelPublicAccessProhibited implements BPSet {
|
|||||||
RestrictPublicBuckets: true
|
RestrictPublicBuckets: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,23 @@
|
|||||||
import {
|
import { S3Client, ListBucketsCommand, GetBucketLoggingCommand, PutBucketLoggingCommand } from '@aws-sdk/client-s3'
|
||||||
S3Client,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ListBucketsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetBucketLoggingCommand,
|
|
||||||
PutBucketLoggingCommand,
|
|
||||||
} from '@aws-sdk/client-s3';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class S3BucketLoggingEnabled implements BPSet {
|
export class S3BucketLoggingEnabled implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3BucketLoggingEnabled',
|
name: 'S3BucketLoggingEnabled',
|
||||||
description: 'Ensures that S3 buckets have logging enabled.',
|
description: 'Ensures that S3 buckets have logging enabled.',
|
||||||
priority: 2,
|
priority: 2,
|
||||||
priorityReason:
|
priorityReason: 'Enabling logging on S3 buckets provides audit and security capabilities.',
|
||||||
'Enabling logging on S3 buckets provides audit and security capabilities.',
|
|
||||||
awsService: 'S3',
|
awsService: 'S3',
|
||||||
awsServiceCategory: 'Buckets',
|
awsServiceCategory: 'Buckets',
|
||||||
bestPracticeCategory: 'Logging',
|
bestPracticeCategory: 'Logging',
|
||||||
@ -32,93 +26,90 @@ export class S3BucketLoggingEnabled implements BPSet {
|
|||||||
name: 'log-destination-bucket',
|
name: 'log-destination-bucket',
|
||||||
description: 'The bucket where access logs should be stored.',
|
description: 'The bucket where access logs should be stored.',
|
||||||
default: '',
|
default: '',
|
||||||
example: 'my-log-bucket',
|
example: 'my-log-bucket'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetBucketLoggingCommand',
|
name: 'GetBucketLoggingCommand',
|
||||||
reason: 'Retrieves the logging configuration for the bucket.',
|
reason: 'Retrieves the logging configuration for the bucket.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutBucketLoggingCommand',
|
name: 'PutBucketLoggingCommand',
|
||||||
reason: 'Enables logging on the bucket.',
|
reason: 'Enables logging on the bucket.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure the destination bucket for logs exists and has proper permissions for logging.'
|
||||||
'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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetBucketLoggingCommand({ Bucket: bucket.Name! }))
|
||||||
new GetBucketLoggingCommand({ Bucket: bucket.Name! })
|
|
||||||
);
|
|
||||||
if (response.LoggingEnabled) {
|
if (response.LoggingEnabled) {
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
@ -126,30 +117,30 @@ export class S3BucketLoggingEnabled implements BPSet {
|
|||||||
) => {
|
) => {
|
||||||
const logDestinationBucket = requiredParametersForFix.find(
|
const logDestinationBucket = requiredParametersForFix.find(
|
||||||
(param) => param.name === 'log-destination-bucket'
|
(param) => param.name === 'log-destination-bucket'
|
||||||
)?.value;
|
)?.value
|
||||||
|
|
||||||
if (!logDestinationBucket) {
|
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) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!;
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutBucketLoggingCommand({
|
new PutBucketLoggingCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
BucketLoggingStatus: {
|
BucketLoggingStatus: {
|
||||||
LoggingEnabled: {
|
LoggingEnabled: {
|
||||||
TargetBucket: logDestinationBucket,
|
TargetBucket: logDestinationBucket,
|
||||||
TargetPrefix: `${bucketName}/logs/`,
|
TargetPrefix: `${bucketName}/logs/`
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
import {
|
import { S3Client, ListBucketsCommand, GetBucketPolicyCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3'
|
||||||
S3Client,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
ListBucketsCommand,
|
import { Memorizer } from '../../Memorizer'
|
||||||
GetBucketPolicyCommand,
|
|
||||||
PutBucketPolicyCommand,
|
|
||||||
} from '@aws-sdk/client-s3';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class S3BucketSSLRequestsOnly implements BPSet {
|
export class S3BucketSSLRequestsOnly implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3BucketSSLRequestsOnly',
|
name: 'S3BucketSSLRequestsOnly',
|
||||||
@ -31,115 +26,105 @@ export class S3BucketSSLRequestsOnly implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetBucketPolicyCommand',
|
name: 'GetBucketPolicyCommand',
|
||||||
reason: 'Retrieves the bucket policy to check for SSL conditions.',
|
reason: 'Retrieves the bucket policy to check for SSL conditions.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutBucketPolicyCommand',
|
name: 'PutBucketPolicyCommand',
|
||||||
reason: 'Updates the bucket policy to enforce SSL requests.',
|
reason: 'Updates the bucket policy to enforce SSL requests.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure existing bucket policies will not conflict with the SSL-only policy.'
|
||||||
'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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucket.Name! }))
|
||||||
new GetBucketPolicyCommand({ Bucket: bucket.Name! })
|
const policy = JSON.parse(response.Policy!)
|
||||||
);
|
|
||||||
const policy = JSON.parse(response.Policy!);
|
|
||||||
const hasSSLCondition = policy.Statement.some(
|
const hasSSLCondition = policy.Statement.some(
|
||||||
(stmt: any) =>
|
(stmt: unknown) =>
|
||||||
stmt.Condition &&
|
stmt.Condition && stmt.Condition.Bool && stmt.Condition.Bool['aws:SecureTransport'] === 'false'
|
||||||
stmt.Condition.Bool &&
|
)
|
||||||
stmt.Condition.Bool['aws:SecureTransport'] === 'false'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasSSLCondition) {
|
if (hasSSLCondition) {
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as any).name === 'NoSuchBucketPolicy') {
|
if ((error as unknown).name === 'NoSuchBucketPolicy') {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
for (const bucketArn of nonCompliantResources) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!;
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
let existingPolicy: any;
|
let existingPolicy: unknown
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucketName }))
|
||||||
new GetBucketPolicyCommand({ Bucket: bucketName })
|
existingPolicy = JSON.parse(response.Policy!)
|
||||||
);
|
|
||||||
existingPolicy = JSON.parse(response.Policy!);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as any).name !== 'NoSuchBucketPolicy') {
|
if ((error as unknown).name !== 'NoSuchBucketPolicy') {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,27 +136,27 @@ export class S3BucketSSLRequestsOnly implements BPSet {
|
|||||||
Resource: [`arn:aws:s3:::${bucketName}/*`, `arn:aws:s3:::${bucketName}`],
|
Resource: [`arn:aws:s3:::${bucketName}/*`, `arn:aws:s3:::${bucketName}`],
|
||||||
Condition: {
|
Condition: {
|
||||||
Bool: {
|
Bool: {
|
||||||
'aws:SecureTransport': 'false',
|
'aws:SecureTransport': 'false'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let updatedPolicy;
|
let updatedPolicy
|
||||||
if (existingPolicy) {
|
if (existingPolicy) {
|
||||||
existingPolicy.Statement.push(sslPolicyStatement);
|
existingPolicy.Statement.push(sslPolicyStatement)
|
||||||
updatedPolicy = JSON.stringify(existingPolicy);
|
updatedPolicy = JSON.stringify(existingPolicy)
|
||||||
} else {
|
} else {
|
||||||
updatedPolicy = this.createSSLOnlyPolicy(bucketName);
|
updatedPolicy = this.createSSLOnlyPolicy(bucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutBucketPolicyCommand({
|
new PutBucketPolicyCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
Policy: updatedPolicy,
|
Policy: updatedPolicy
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly createSSLOnlyPolicy = (bucketName: string): string => {
|
private readonly createSSLOnlyPolicy = (bucketName: string): string => {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
@ -185,16 +170,16 @@ export class S3BucketSSLRequestsOnly implements BPSet {
|
|||||||
Resource: [`arn:aws:s3:::${bucketName}/*`, `arn:aws:s3:::${bucketName}`],
|
Resource: [`arn:aws:s3:::${bucketName}/*`, `arn:aws:s3:::${bucketName}`],
|
||||||
Condition: {
|
Condition: {
|
||||||
Bool: {
|
Bool: {
|
||||||
'aws:SecureTransport': 'false',
|
'aws:SecureTransport': 'false'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,27 +2,28 @@ import {
|
|||||||
S3Client,
|
S3Client,
|
||||||
ListBucketsCommand,
|
ListBucketsCommand,
|
||||||
GetBucketVersioningCommand,
|
GetBucketVersioningCommand,
|
||||||
PutBucketVersioningCommand,
|
PutBucketVersioningCommand
|
||||||
} from '@aws-sdk/client-s3';
|
} from '@aws-sdk/client-s3'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class S3BucketVersioningEnabled implements BPSet {
|
export class S3BucketVersioningEnabled implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3BucketVersioningEnabled',
|
name: 'S3BucketVersioningEnabled',
|
||||||
description: 'Ensures that versioning is enabled on all S3 buckets.',
|
description: 'Ensures that versioning is enabled on all S3 buckets.',
|
||||||
priority: 1,
|
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',
|
awsService: 'S3',
|
||||||
awsServiceCategory: 'Buckets',
|
awsServiceCategory: 'Buckets',
|
||||||
bestPracticeCategory: 'Data Protection',
|
bestPracticeCategory: 'Data Protection',
|
||||||
@ -31,101 +32,96 @@ export class S3BucketVersioningEnabled implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetBucketVersioningCommand',
|
name: 'GetBucketVersioningCommand',
|
||||||
reason: 'Retrieve the current versioning status of the bucket.',
|
reason: 'Retrieve the current versioning status of the bucket.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutBucketVersioningCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetBucketVersioningCommand({ Bucket: bucket.Name! }))
|
||||||
new GetBucketVersioningCommand({ Bucket: bucket.Name! })
|
|
||||||
);
|
|
||||||
if (response.Status === 'Enabled') {
|
if (response.Status === 'Enabled') {
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
for (const bucketArn of nonCompliantResources) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!;
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutBucketVersioningCommand({
|
new PutBucketVersioningCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
VersioningConfiguration: {
|
VersioningConfiguration: {
|
||||||
Status: 'Enabled',
|
Status: 'Enabled'
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,21 @@ import {
|
|||||||
S3Client,
|
S3Client,
|
||||||
ListBucketsCommand,
|
ListBucketsCommand,
|
||||||
GetBucketEncryptionCommand,
|
GetBucketEncryptionCommand,
|
||||||
PutBucketEncryptionCommand,
|
PutBucketEncryptionCommand
|
||||||
} from '@aws-sdk/client-s3';
|
} from '@aws-sdk/client-s3'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class S3DefaultEncryptionKMS implements BPSet {
|
export class S3DefaultEncryptionKMS implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3DefaultEncryptionKMS',
|
name: 'S3DefaultEncryptionKMS',
|
||||||
@ -31,117 +31,114 @@ export class S3DefaultEncryptionKMS implements BPSet {
|
|||||||
name: 'kms-key-id',
|
name: 'kms-key-id',
|
||||||
description: 'The KMS Key ID used for bucket encryption.',
|
description: 'The KMS Key ID used for bucket encryption.',
|
||||||
default: '',
|
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,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetBucketEncryptionCommand',
|
name: 'GetBucketEncryptionCommand',
|
||||||
reason: 'Retrieve the encryption configuration for a bucket.',
|
reason: 'Retrieve the encryption configuration for a bucket.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutBucketEncryptionCommand',
|
name: 'PutBucketEncryptionCommand',
|
||||||
reason: 'Enable KMS encryption for the bucket.',
|
reason: 'Enable KMS encryption for the bucket.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
adviseBeforeFixFunction:
|
adviseBeforeFixFunction: 'Ensure the KMS key is properly configured with necessary permissions for S3 operations.'
|
||||||
'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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(new GetBucketEncryptionCommand({ Bucket: bucket.Name! }))
|
||||||
new GetBucketEncryptionCommand({ Bucket: bucket.Name! })
|
const encryption = response.ServerSideEncryptionConfiguration!
|
||||||
);
|
|
||||||
const encryption = response.ServerSideEncryptionConfiguration!;
|
|
||||||
const isKmsEnabled = encryption.Rules?.some(
|
const isKmsEnabled = encryption.Rules?.some(
|
||||||
(rule) =>
|
(rule) =>
|
||||||
rule.ApplyServerSideEncryptionByDefault &&
|
rule.ApplyServerSideEncryptionByDefault &&
|
||||||
rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm === 'aws:kms'
|
rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm === 'aws:kms'
|
||||||
);
|
)
|
||||||
|
|
||||||
if (isKmsEnabled) {
|
if (isKmsEnabled) {
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as any).name === 'ServerSideEncryptionConfigurationNotFoundError') {
|
if ((error as unknown).name === 'ServerSideEncryptionConfigurationNotFoundError') {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: 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) {
|
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) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!;
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutBucketEncryptionCommand({
|
new PutBucketEncryptionCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
@ -150,18 +147,18 @@ export class S3DefaultEncryptionKMS implements BPSet {
|
|||||||
{
|
{
|
||||||
ApplyServerSideEncryptionByDefault: {
|
ApplyServerSideEncryptionByDefault: {
|
||||||
SSEAlgorithm: 'aws:kms',
|
SSEAlgorithm: 'aws:kms',
|
||||||
KMSMasterKeyID: kmsKeyId,
|
KMSMasterKeyID: kmsKeyId
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,27 +2,28 @@ import {
|
|||||||
S3Client,
|
S3Client,
|
||||||
ListBucketsCommand,
|
ListBucketsCommand,
|
||||||
GetBucketNotificationConfigurationCommand,
|
GetBucketNotificationConfigurationCommand,
|
||||||
PutBucketNotificationConfigurationCommand,
|
PutBucketNotificationConfigurationCommand
|
||||||
} from '@aws-sdk/client-s3';
|
} from '@aws-sdk/client-s3'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer';
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class S3EventNotificationsEnabled implements BPSet {
|
export class S3EventNotificationsEnabled implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3EventNotificationsEnabled',
|
name: 'S3EventNotificationsEnabled',
|
||||||
description: 'Ensures that S3 buckets have event notifications configured.',
|
description: 'Ensures that S3 buckets have event notifications configured.',
|
||||||
priority: 2,
|
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',
|
awsService: 'S3',
|
||||||
awsServiceCategory: 'Buckets',
|
awsServiceCategory: 'Buckets',
|
||||||
bestPracticeCategory: 'Monitoring & Automation',
|
bestPracticeCategory: 'Monitoring & Automation',
|
||||||
@ -31,118 +32,108 @@ export class S3EventNotificationsEnabled implements BPSet {
|
|||||||
name: 'lambda-function-arn',
|
name: 'lambda-function-arn',
|
||||||
description: 'ARN of the Lambda function to invoke for bucket events.',
|
description: 'ARN of the Lambda function to invoke for bucket events.',
|
||||||
default: '',
|
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',
|
name: 'event-type',
|
||||||
description: 'S3 event type to trigger the notification.',
|
description: 'S3 event type to trigger the notification.',
|
||||||
default: 's3:ObjectCreated:*',
|
default: 's3:ObjectCreated:*',
|
||||||
example: 's3:ObjectCreated:Put',
|
example: 's3:ObjectCreated:Put'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
isFixFunctionUsesDestructiveCommand: false,
|
isFixFunctionUsesDestructiveCommand: false,
|
||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'GetBucketNotificationConfigurationCommand',
|
name: 'GetBucketNotificationConfigurationCommand',
|
||||||
reason: 'Retrieve the current notification configuration for a bucket.',
|
reason: 'Retrieve the current notification configuration for a bucket.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [
|
commandUsedInFixFunction: [
|
||||||
{
|
{
|
||||||
name: 'PutBucketNotificationConfigurationCommand',
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
const response = await this.memoClient.send(
|
const response = await this.memoClient.send(
|
||||||
new GetBucketNotificationConfigurationCommand({ Bucket: bucket.Name! })
|
new GetBucketNotificationConfigurationCommand({ Bucket: bucket.Name! })
|
||||||
);
|
)
|
||||||
if (
|
if (response.LambdaFunctionConfigurations || response.QueueConfigurations || response.TopicConfigurations) {
|
||||||
response.LambdaFunctionConfigurations ||
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
response.QueueConfigurations ||
|
|
||||||
response.TopicConfigurations
|
|
||||||
) {
|
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly fixImpl = async (
|
private readonly fixImpl = async (
|
||||||
nonCompliantResources: string[],
|
nonCompliantResources: string[],
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
requiredParametersForFix: { name: string; value: string }[]
|
||||||
) => {
|
) => {
|
||||||
const lambdaArn = requiredParametersForFix.find(
|
const lambdaArn = requiredParametersForFix.find((param) => param.name === 'lambda-function-arn')?.value
|
||||||
(param) => param.name === 'lambda-function-arn'
|
const eventType = requiredParametersForFix.find((param) => param.name === 'event-type')?.value
|
||||||
)?.value;
|
|
||||||
const eventType = requiredParametersForFix.find(
|
|
||||||
(param) => param.name === 'event-type'
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
if (!lambdaArn || !eventType) {
|
if (!lambdaArn || !eventType) {
|
||||||
throw new Error(
|
throw new Error("Required parameters 'lambda-function-arn' and/or 'event-type' are missing.")
|
||||||
"Required parameters 'lambda-function-arn' and/or 'event-type' are missing."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const bucketArn of nonCompliantResources) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!;
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
await this.client.send(
|
await this.client.send(
|
||||||
new PutBucketNotificationConfigurationCommand({
|
new PutBucketNotificationConfigurationCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
@ -150,17 +141,17 @@ export class S3EventNotificationsEnabled implements BPSet {
|
|||||||
LambdaFunctionConfigurations: [
|
LambdaFunctionConfigurations: [
|
||||||
{
|
{
|
||||||
LambdaFunctionArn: lambdaArn,
|
LambdaFunctionArn: lambdaArn,
|
||||||
Events: [eventType as any],
|
Events: [eventType as unknown]
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
import {
|
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3'
|
||||||
S3Client,
|
import { BackupClient, ListRecoveryPointsByResourceCommand } from '@aws-sdk/client-backup'
|
||||||
ListBucketsCommand,
|
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
||||||
} from '@aws-sdk/client-s3';
|
import { Memorizer } from '../../Memorizer'
|
||||||
import { BackupClient, ListRecoveryPointsByResourceCommand } from '@aws-sdk/client-backup';
|
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types';
|
|
||||||
import { Memorizer } from '../../Memorizer';
|
|
||||||
|
|
||||||
export class S3LastBackupRecoveryPointCreated implements BPSet {
|
export class S3LastBackupRecoveryPointCreated implements BPSet {
|
||||||
private readonly client = new S3Client({});
|
private readonly client = new S3Client({})
|
||||||
private readonly memoClient = Memorizer.memo(this.client);
|
private readonly memoClient = Memorizer.memo(this.client)
|
||||||
private readonly backupClient = Memorizer.memo(new BackupClient({}));
|
private readonly backupClient = Memorizer.memo(new BackupClient({}))
|
||||||
|
|
||||||
private readonly stats: BPSetStats = {
|
private readonly stats: BPSetStats = {
|
||||||
compliantResources: [],
|
compliantResources: [],
|
||||||
nonCompliantResources: [],
|
nonCompliantResources: [],
|
||||||
status: 'LOADED',
|
status: 'LOADED',
|
||||||
errorMessage: [],
|
errorMessage: []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly getMetadata = (): BPSetMetadata => ({
|
public readonly getMetadata = (): BPSetMetadata => ({
|
||||||
name: 'S3LastBackupRecoveryPointCreated',
|
name: 'S3LastBackupRecoveryPointCreated',
|
||||||
@ -31,77 +28,72 @@ export class S3LastBackupRecoveryPointCreated implements BPSet {
|
|||||||
commandUsedInCheckFunction: [
|
commandUsedInCheckFunction: [
|
||||||
{
|
{
|
||||||
name: 'ListRecoveryPointsByResourceCommand',
|
name: 'ListRecoveryPointsByResourceCommand',
|
||||||
reason: 'Checks for recent recovery points for the S3 bucket.',
|
reason: 'Checks for recent recovery points for the S3 bucket.'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
commandUsedInFixFunction: [],
|
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 = () => {
|
public readonly clearStats = () => {
|
||||||
this.stats.compliantResources = [];
|
this.stats.compliantResources = []
|
||||||
this.stats.nonCompliantResources = [];
|
this.stats.nonCompliantResources = []
|
||||||
this.stats.status = 'LOADED';
|
this.stats.status = 'LOADED'
|
||||||
this.stats.errorMessage = [];
|
this.stats.errorMessage = []
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly check = async () => {
|
public readonly check = async () => {
|
||||||
this.stats.status = 'CHECKING';
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.checkImpl()
|
await this.checkImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED';
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: err.message,
|
message: err.message
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
private readonly checkImpl = async () => {
|
private readonly checkImpl = async () => {
|
||||||
const compliantResources: string[] = [];
|
const compliantResources: string[] = []
|
||||||
const nonCompliantResources: string[] = [];
|
const nonCompliantResources: string[] = []
|
||||||
const buckets = await this.getBuckets();
|
const buckets = await this.getBuckets()
|
||||||
|
|
||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
const recoveryPoints = await this.backupClient.send(
|
const recoveryPoints = await this.backupClient.send(
|
||||||
new ListRecoveryPointsByResourceCommand({
|
new ListRecoveryPointsByResourceCommand({
|
||||||
ResourceArn: `arn:aws:s3:::${bucket.Name!}`,
|
ResourceArn: `arn:aws:s3:::${bucket.Name!}`
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
if (
|
if (recoveryPoints.RecoveryPoints && recoveryPoints.RecoveryPoints.length > 0) {
|
||||||
recoveryPoints.RecoveryPoints &&
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
recoveryPoints.RecoveryPoints.length > 0
|
|
||||||
) {
|
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`);
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stats.compliantResources = compliantResources;
|
this.stats.compliantResources = compliantResources
|
||||||
this.stats.nonCompliantResources = nonCompliantResources;
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
};
|
}
|
||||||
|
|
||||||
public readonly fix = async () => {
|
public readonly fix = async () => {
|
||||||
this.stats.status = 'ERROR';
|
this.stats.status = 'ERROR'
|
||||||
this.stats.errorMessage.push({
|
this.stats.errorMessage.push({
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: 'Fixing recovery points requires custom implementation for backup setup.',
|
message: 'Fixing recovery points requires custom implementation for backup setup.'
|
||||||
});
|
})
|
||||||
throw new Error(
|
throw new Error('Fixing recovery points requires custom implementation for backup setup.')
|
||||||
'Fixing recovery points requires custom implementation for backup setup.'
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly getBuckets = async () => {
|
private readonly getBuckets = async () => {
|
||||||
const response = await this.memoClient.send(new ListBucketsCommand({}));
|
const response = await this.memoClient.send(new ListBucketsCommand({}))
|
||||||
return response.Buckets || [];
|
return response.Buckets || []
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user