refactor: improve type safety and enhance AWS SDK integration
All checks were successful
/ deploy_site (push) Successful in 2m14s
All checks were successful
/ deploy_site (push) Successful in 2m14s
- Updated the `Memorizer` class to use a more specific `AnyClient` type for AWS SDK clients. - Enhanced type safety in various AWS service classes by replacing `unknown` casts with specific types (e.g., `AuthorizationType`, `DistributionConfig`, `PolicyDocument`). - Refactored error handling to ensure proper type assertions for error objects across multiple services. - Simplified the `fix` method signatures in several classes to accept only non-compliant resources, improving clarity and usability.
This commit is contained in:
@ -1,6 +1,9 @@
|
|||||||
import { Client } from '@smithy/smithy-client'
|
import { Client } from '@smithy/smithy-client'
|
||||||
import shajs from 'sha.js'
|
import shajs from 'sha.js'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type AnyClient = Client<any, any, any, any>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memorize AWS SDK operation results.
|
* Memorize AWS SDK operation results.
|
||||||
* This util class tend to be always re-use AWS SDK Client
|
* This util class tend to be always re-use AWS SDK Client
|
||||||
@ -15,7 +18,7 @@ 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<unknown, unknown, unknown, unknown>, salt = '') {
|
public static memo(client: AnyClient, 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')
|
||||||
|
|
||||||
@ -35,7 +38,7 @@ export class Memorizer {
|
|||||||
|
|
||||||
private memorized = new Map<string, unknown>()
|
private memorized = new Map<string, unknown>()
|
||||||
|
|
||||||
private constructor(private client: Client<unknown, unknown, unknown, unknown>) {}
|
private constructor(private client: AnyClient) {}
|
||||||
|
|
||||||
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])
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { ApiGatewayV2Client, GetApisCommand, GetRoutesCommand, UpdateRouteCommand } from '@aws-sdk/client-apigatewayv2'
|
import {
|
||||||
|
ApiGatewayV2Client,
|
||||||
|
AuthorizationType,
|
||||||
|
GetApisCommand,
|
||||||
|
GetRoutesCommand,
|
||||||
|
UpdateRouteCommand
|
||||||
|
} from '@aws-sdk/client-apigatewayv2'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer'
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
@ -135,7 +141,7 @@ export class APIGatewayV2AuthorizationTypeConfigured implements BPSet {
|
|||||||
new UpdateRouteCommand({
|
new UpdateRouteCommand({
|
||||||
ApiId: api.ApiId!,
|
ApiId: api.ApiId!,
|
||||||
RouteId: route.RouteId!,
|
RouteId: route.RouteId!,
|
||||||
AuthorizationType: authorizationType as unknown
|
AuthorizationType: authorizationType as AuthorizationType
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand
|
UpdateDistributionCommand,
|
||||||
|
DistributionConfig
|
||||||
} 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'
|
||||||
@ -147,7 +148,7 @@ export class CloudFrontAccessLogsEnabled implements BPSet {
|
|||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as unknown
|
DistributionConfig: updatedConfig as DistributionConfig
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand
|
UpdateDistributionCommand,
|
||||||
|
DistributionConfig
|
||||||
} 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'
|
||||||
@ -137,7 +138,7 @@ export class CloudFrontAssociatedWithWAF implements BPSet {
|
|||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as unknown
|
DistributionConfig: updatedConfig as DistributionConfig
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand
|
UpdateDistributionCommand,
|
||||||
|
DistributionConfig
|
||||||
} 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'
|
||||||
@ -143,7 +144,7 @@ export class CloudFrontDefaultRootObjectConfigured implements BPSet {
|
|||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as unknown
|
DistributionConfig: updatedConfig as DistributionConfig
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand
|
UpdateDistributionCommand,
|
||||||
|
DistributionConfig
|
||||||
} 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'
|
||||||
@ -151,7 +152,7 @@ export class CloudFrontNoDeprecatedSSLProtocols implements BPSet {
|
|||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as unknown
|
DistributionConfig: updatedConfig as DistributionConfig
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand
|
UpdateDistributionCommand,
|
||||||
|
DistributionConfig
|
||||||
} 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'
|
||||||
@ -160,7 +161,7 @@ export class CloudFrontS3OriginAccessControlEnabled implements BPSet {
|
|||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as unknown
|
DistributionConfig: updatedConfig as DistributionConfig
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ import {
|
|||||||
CloudFrontClient,
|
CloudFrontClient,
|
||||||
ListDistributionsCommand,
|
ListDistributionsCommand,
|
||||||
GetDistributionCommand,
|
GetDistributionCommand,
|
||||||
UpdateDistributionCommand
|
UpdateDistributionCommand,
|
||||||
|
DistributionConfig
|
||||||
} 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'
|
||||||
@ -145,7 +146,7 @@ export class CloudFrontViewerPolicyHTTPS implements BPSet {
|
|||||||
new UpdateDistributionCommand({
|
new UpdateDistributionCommand({
|
||||||
Id: distributionId,
|
Id: distributionId,
|
||||||
IfMatch: etag,
|
IfMatch: etag,
|
||||||
DistributionConfig: updatedConfig as unknown
|
DistributionConfig: updatedConfig as DistributionConfig
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { CloudWatchClient, DescribeAlarmsCommand, PutMetricAlarmCommand } from '@aws-sdk/client-cloudwatch'
|
import {
|
||||||
|
CloudWatchClient,
|
||||||
|
ComparisonOperator,
|
||||||
|
DescribeAlarmsCommand,
|
||||||
|
PutMetricAlarmCommand,
|
||||||
|
Statistic
|
||||||
|
} from '@aws-sdk/client-cloudwatch'
|
||||||
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
import { BPSet, BPSetFixFn, BPSetStats } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer'
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
@ -88,7 +94,7 @@ export class CloudWatchAlarmSettingsCheck implements BPSet {
|
|||||||
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: Record<string, string | null> = {
|
||||||
MetricName: '', // Required
|
MetricName: '', // Required
|
||||||
Threshold: null,
|
Threshold: null,
|
||||||
EvaluationPeriods: null,
|
EvaluationPeriods: null,
|
||||||
@ -100,7 +106,7 @@ export class CloudWatchAlarmSettingsCheck implements BPSet {
|
|||||||
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 unknown)[k] !== null)) {
|
for (const key of Object.keys(parameters).filter((k) => parameters[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
|
||||||
@ -144,8 +150,8 @@ 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 unknown,
|
ComparisonOperator: requiredSettings['comparison-operator'] as ComparisonOperator,
|
||||||
Statistic: requiredSettings['statistic'] as unknown
|
Statistic: requiredSettings['statistic'] as Statistic
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -125,9 +125,9 @@ export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet {
|
|||||||
new UpdateProjectCommand({
|
new UpdateProjectCommand({
|
||||||
name: projectName,
|
name: projectName,
|
||||||
environment: {
|
environment: {
|
||||||
...projectToFix.environment,
|
...projectToFix.environment!,
|
||||||
privilegedMode: false
|
privilegedMode: false
|
||||||
} as unknown
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ export class ECRPrivateLifecyclePolicyConfigured implements BPSet {
|
|||||||
)
|
)
|
||||||
compliantResources.push(repository.repositoryArn!)
|
compliantResources.push(repository.repositoryArn!)
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error.name === 'LifecyclePolicyNotFoundException') {
|
if ((error as Error).name === 'LifecyclePolicyNotFoundException') {
|
||||||
nonCompliantResources.push(repository.repositoryArn!)
|
nonCompliantResources.push(repository.repositoryArn!)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
CreatePolicyVersionCommand,
|
CreatePolicyVersionCommand,
|
||||||
DeletePolicyVersionCommand
|
DeletePolicyVersionCommand
|
||||||
} from '@aws-sdk/client-iam'
|
} from '@aws-sdk/client-iam'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
import { BPSet, BPSetMetadata, BPSetStats, PolicyDocument } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer'
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class IAMPolicyNoStatementsWithFullAccess implements BPSet {
|
export class IAMPolicyNoStatementsWithFullAccess implements BPSet {
|
||||||
@ -88,9 +88,11 @@ export class IAMPolicyNoStatementsWithFullAccess implements BPSet {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const policyDocument = JSON.parse(decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string))
|
const policyDocument = JSON.parse(
|
||||||
|
decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string)
|
||||||
|
) as PolicyDocument
|
||||||
|
|
||||||
const hasFullAccess = policyDocument.Statement.some((statement: unknown) => {
|
const hasFullAccess = policyDocument.Statement.some((statement) => {
|
||||||
if (statement.Effect === 'Deny') return false
|
if (statement.Effect === 'Deny') return false
|
||||||
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
|
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
|
||||||
return actions.some((action: string) => action.endsWith(':*'))
|
return actions.some((action: string) => action.endsWith(':*'))
|
||||||
@ -143,9 +145,11 @@ export class IAMPolicyNoStatementsWithFullAccess implements BPSet {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const policyDocument = JSON.parse(decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string))
|
const policyDocument = JSON.parse(
|
||||||
|
decodeURIComponent(policyVersionResponse.PolicyVersion!.Document as string)
|
||||||
|
) as PolicyDocument
|
||||||
|
|
||||||
policyDocument.Statement = policyDocument.Statement.filter((statement: unknown) => {
|
policyDocument.Statement = policyDocument.Statement.filter((statement) => {
|
||||||
if (statement.Effect === 'Deny') return true
|
if (statement.Effect === 'Deny') return true
|
||||||
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
|
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action]
|
||||||
return !actions.some((action: string) => action.endsWith(':*'))
|
return !actions.some((action: string) => action.endsWith(':*'))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { LambdaClient, ListFunctionsCommand, GetPolicyCommand, RemovePermissionCommand } from '@aws-sdk/client-lambda'
|
import { LambdaClient, ListFunctionsCommand, GetPolicyCommand, RemovePermissionCommand } from '@aws-sdk/client-lambda'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
import { BPSet, BPSetMetadata, BPSetStats, PolicyDocument } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer'
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
||||||
@ -75,10 +75,10 @@ export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
|||||||
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!) as PolicyDocument
|
||||||
|
|
||||||
const hasPublicAccess = policy.Statement.some(
|
const hasPublicAccess = policy.Statement.some(
|
||||||
(statement: unknown) => statement.Principal === '*' || statement.Principal?.AWS === '*'
|
(statement) => statement.Principal === '*' || statement.Principal?.AWS === '*'
|
||||||
)
|
)
|
||||||
|
|
||||||
if (hasPublicAccess) {
|
if (hasPublicAccess) {
|
||||||
@ -87,7 +87,7 @@ export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
|||||||
compliantResources.push(func.FunctionArn!)
|
compliantResources.push(func.FunctionArn!)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as unknown).name === 'ResourceNotFoundException') {
|
if ((error as Error).name === 'ResourceNotFoundException') {
|
||||||
compliantResources.push(func.FunctionArn!)
|
compliantResources.push(func.FunctionArn!)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
@ -132,7 +132,7 @@ export class LambdaFunctionPublicAccessProhibited implements BPSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as unknown).name !== 'ResourceNotFoundException') {
|
if ((error as Error).name !== 'ResourceNotFoundException') {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,13 +81,10 @@ export class RDSClusterIAMAuthenticationEnabled implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -83,13 +83,10 @@ export class RDSClusterMultiAZEnabled implements BPSet {
|
|||||||
this.stats.nonCompliantResources = nonCompliantResources
|
this.stats.nonCompliantResources = nonCompliantResources
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly fix = async (
|
public readonly fix = async () => {
|
||||||
nonCompliantResources: string[],
|
|
||||||
requiredParametersForFix: { name: string; value: string }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -81,13 +81,10 @@ export class RDSInstancePublicAccessCheck implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -91,13 +91,10 @@ export class RDSLoggingEnabled implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -80,7 +80,7 @@ export class S3BucketDefaultLockEnabled implements BPSet {
|
|||||||
await this.memoClient.send(new GetObjectLockConfigurationCommand({ Bucket: bucket.Name! }))
|
await this.memoClient.send(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 unknown).name === 'ObjectLockConfigurationNotFoundError') {
|
if ((error as Error).name === 'ObjectLockConfigurationNotFoundError') {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
@ -92,13 +92,10 @@ export class S3BucketDefaultLockEnabled implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -96,13 +96,10 @@ export class S3BucketLevelPublicAccessProhibited implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { S3Client, ListBucketsCommand, GetBucketPolicyCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3'
|
import { S3Client, ListBucketsCommand, GetBucketPolicyCommand, PutBucketPolicyCommand } from '@aws-sdk/client-s3'
|
||||||
import { BPSet, BPSetMetadata, BPSetStats } from '../../types'
|
import { BPSet, BPSetMetadata, BPSetStats, PolicyDocument, Statement } from '../../types'
|
||||||
import { Memorizer } from '../../Memorizer'
|
import { Memorizer } from '../../Memorizer'
|
||||||
|
|
||||||
export class S3BucketSSLRequestsOnly implements BPSet {
|
export class S3BucketSSLRequestsOnly implements BPSet {
|
||||||
@ -71,10 +71,9 @@ export class S3BucketSSLRequestsOnly implements BPSet {
|
|||||||
for (const bucket of buckets) {
|
for (const bucket of buckets) {
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucket.Name! }))
|
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucket.Name! }))
|
||||||
const policy = JSON.parse(response.Policy!)
|
const policy = JSON.parse(response.Policy!) as PolicyDocument
|
||||||
const hasSSLCondition = policy.Statement.some(
|
const hasSSLCondition = policy.Statement.some(
|
||||||
(stmt: unknown) =>
|
(stmt) => stmt.Condition && stmt.Condition.Bool && stmt.Condition.Bool['aws:SecureTransport'] === 'false'
|
||||||
stmt.Condition && stmt.Condition.Bool && stmt.Condition.Bool['aws:SecureTransport'] === 'false'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (hasSSLCondition) {
|
if (hasSSLCondition) {
|
||||||
@ -83,7 +82,7 @@ export class S3BucketSSLRequestsOnly implements BPSet {
|
|||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as unknown).name === 'NoSuchBucketPolicy') {
|
if ((error as Error).name === 'NoSuchBucketPolicy') {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
@ -95,13 +94,10 @@ export class S3BucketSSLRequestsOnly implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
@ -117,18 +113,18 @@ export class S3BucketSSLRequestsOnly implements BPSet {
|
|||||||
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
private readonly fixImpl = async (nonCompliantResources: string[]) => {
|
||||||
for (const bucketArn of nonCompliantResources) {
|
for (const bucketArn of nonCompliantResources) {
|
||||||
const bucketName = bucketArn.split(':::')[1]!
|
const bucketName = bucketArn.split(':::')[1]!
|
||||||
let existingPolicy: unknown
|
let existingPolicy
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucketName }))
|
const response = await this.memoClient.send(new GetBucketPolicyCommand({ Bucket: bucketName }))
|
||||||
existingPolicy = JSON.parse(response.Policy!)
|
existingPolicy = JSON.parse(response.Policy!) as PolicyDocument
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as unknown).name !== 'NoSuchBucketPolicy') {
|
if ((error as Error).name !== 'NoSuchBucketPolicy') {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sslPolicyStatement = {
|
const sslPolicyStatement: Statement = {
|
||||||
Sid: 'DenyNonSSLRequests',
|
Sid: 'DenyNonSSLRequests',
|
||||||
Effect: 'Deny',
|
Effect: 'Deny',
|
||||||
Principal: '*',
|
Principal: '*',
|
||||||
|
@ -87,13 +87,10 @@ export class S3BucketVersioningEnabled implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -96,7 +96,7 @@ export class S3DefaultEncryptionKMS implements BPSet {
|
|||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as unknown).name === 'ServerSideEncryptionConfigurationNotFoundError') {
|
if ((error as Error).name === 'ServerSideEncryptionConfigurationNotFoundError') {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -2,7 +2,8 @@ import {
|
|||||||
S3Client,
|
S3Client,
|
||||||
ListBucketsCommand,
|
ListBucketsCommand,
|
||||||
GetBucketNotificationConfigurationCommand,
|
GetBucketNotificationConfigurationCommand,
|
||||||
PutBucketNotificationConfigurationCommand
|
PutBucketNotificationConfigurationCommand,
|
||||||
|
Event
|
||||||
} 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'
|
||||||
@ -141,7 +142,7 @@ export class S3EventNotificationsEnabled implements BPSet {
|
|||||||
LambdaFunctionConfigurations: [
|
LambdaFunctionConfigurations: [
|
||||||
{
|
{
|
||||||
LambdaFunctionArn: lambdaArn,
|
LambdaFunctionArn: lambdaArn,
|
||||||
Events: [eventType as unknown]
|
Events: [eventType] as Event[]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ export class S3LifecyclePolicyCheck implements BPSet {
|
|||||||
await this.memoClient.send(new GetBucketLifecycleConfigurationCommand({ Bucket: bucket.Name! }))
|
await this.memoClient.send(new GetBucketLifecycleConfigurationCommand({ Bucket: bucket.Name! }))
|
||||||
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
compliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as unknown).name === 'NoSuchLifecycleConfiguration') {
|
if ((error as Error).name === 'NoSuchLifecycleConfiguration') {
|
||||||
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
nonCompliantResources.push(`arn:aws:s3:::${bucket.Name!}`)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -81,13 +81,10 @@ export class SecretsManagerRotationEnabledCheck implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -93,13 +93,10 @@ export class SecretsManagerScheduledRotationSuccessCheck implements BPSet {
|
|||||||
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 }[]
|
|
||||||
) => {
|
|
||||||
this.stats.status = 'CHECKING'
|
this.stats.status = 'CHECKING'
|
||||||
|
|
||||||
await this.fixImpl(nonCompliantResources, requiredParametersForFix)
|
await this.fixImpl(nonCompliantResources)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.stats.status = 'FINISHED'
|
this.stats.status = 'FINISHED'
|
||||||
})
|
})
|
||||||
|
@ -76,7 +76,7 @@ export class SecurityHubEnabled implements BPSet {
|
|||||||
await this.memoSecurityHubClient.send(new DescribeHubCommand({}))
|
await this.memoSecurityHubClient.send(new DescribeHubCommand({}))
|
||||||
compliantResources.push(awsAccountId)
|
compliantResources.push(awsAccountId)
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error.name === 'InvalidAccessException') {
|
if ((error as Error).name === 'InvalidAccessException') {
|
||||||
nonCompliantResources.push(awsAccountId)
|
nonCompliantResources.push(awsAccountId)
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
|
@ -80,7 +80,7 @@ export class SNSEncryptedKMS implements BPSet {
|
|||||||
const topics = await this.getTopics()
|
const topics = await this.getTopics()
|
||||||
|
|
||||||
for (const topic of topics) {
|
for (const topic of topics) {
|
||||||
if ((topic as unknown).KmsMasterKeyId) {
|
if (topic.KmsMasterKeyId) {
|
||||||
compliantResources.push(topic.TopicArn!)
|
compliantResources.push(topic.TopicArn!)
|
||||||
} else {
|
} else {
|
||||||
nonCompliantResources.push(topic.TopicArn!)
|
nonCompliantResources.push(topic.TopicArn!)
|
||||||
@ -135,7 +135,7 @@ export class SNSEncryptedKMS implements BPSet {
|
|||||||
const topicsResponse = await this.memoClient.send(new ListTopicsCommand({}))
|
const topicsResponse = await this.memoClient.send(new ListTopicsCommand({}))
|
||||||
const topics = topicsResponse.Topics || []
|
const topics = topicsResponse.Topics || []
|
||||||
|
|
||||||
const topicDetails = []
|
const topicDetails: Record<string, string>[] = []
|
||||||
for (const topic of topics) {
|
for (const topic of topics) {
|
||||||
const attributes = await this.memoClient.send(new GetTopicAttributesCommand({ TopicArn: topic.TopicArn! }))
|
const attributes = await this.memoClient.send(new GetTopicAttributesCommand({ TopicArn: topic.TopicArn! }))
|
||||||
topicDetails.push({ ...attributes.Attributes, TopicArn: topic.TopicArn! })
|
topicDetails.push({ ...attributes.Attributes, TopicArn: topic.TopicArn! })
|
||||||
|
21
src/types.d.ts
vendored
21
src/types.d.ts
vendored
@ -57,3 +57,24 @@ export interface BPSetStats {
|
|||||||
message: string
|
message: string
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
interface PolicyDocument {
|
||||||
|
Version: '2012-10-17' | '2008-10-17' // Only valid AWS IAM policy versions
|
||||||
|
Statement: Statement[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Statement {
|
||||||
|
Sid?: string // Optional statement ID
|
||||||
|
Effect: 'Allow' | 'Deny' // Effect of the statement
|
||||||
|
Action: string | string[] // Action(s) the statement applies to
|
||||||
|
Resource: string | string[] // Resource(s) the statement applies to
|
||||||
|
Principal?:
|
||||||
|
| '*'
|
||||||
|
| {
|
||||||
|
[key: string]: string | string[]
|
||||||
|
} // Optional principal(s) the statement applies to, can be "*"
|
||||||
|
Condition?: {
|
||||||
|
[key: string]: {
|
||||||
|
[operator: string]: string | string[]
|
||||||
|
}
|
||||||
|
} // Optional conditions for the statement
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user