refactor: improve type safety and enhance AWS SDK integration
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:
2025-01-16 17:03:13 +09:00
parent ae1fd64367
commit cd1d80a0b7
29 changed files with 117 additions and 101 deletions

View File

@ -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])

View File

@ -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
}) })
) )
} }

View File

@ -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
}) })
) )
} }

View File

@ -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
}) })
) )
} }

View File

@ -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
}) })
) )
} }

View File

@ -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
}) })
) )
} }

View File

@ -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
}) })
) )
} }

View File

@ -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
}) })
) )
} }

View File

@ -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
}) })
) )
} }

View File

@ -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 }
}) })
) )
} }

View File

@ -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

View File

@ -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(':*'))

View File

@ -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
} }
} }

View File

@ -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'
}) })

View File

@ -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'
}) })

View File

@ -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'
}) })

View File

@ -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'
}) })

View File

@ -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'
}) })

View File

@ -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'
}) })

View File

@ -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: '*',

View File

@ -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'
}) })

View File

@ -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

View File

@ -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[]
} }
] ]
} }

View File

@ -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

View File

@ -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'
}) })

View File

@ -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'
}) })

View File

@ -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

View File

@ -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
View File

@ -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
}