feat: add more and more bps

This commit is contained in:
2024-12-24 09:32:56 +09:00
parent e845d4a9c3
commit beefba9a18
22 changed files with 1345 additions and 3 deletions

View File

@ -31,6 +31,9 @@
"@aws-sdk/client-rds": "^3.716.0",
"@aws-sdk/client-s3": "^3.717.0",
"@aws-sdk/client-s3-control": "^3.716.0",
"@aws-sdk/client-secrets-manager": "^3.716.0",
"@aws-sdk/client-securityhub": "^3.716.0",
"@aws-sdk/client-sns": "^3.716.0",
"@aws-sdk/client-ssm": "^3.716.0",
"@aws-sdk/client-sts": "^3.716.0",
"@aws-sdk/client-wafv2": "^3.716.0",

163
pnpm-lock.yaml generated
View File

@ -74,6 +74,15 @@ importers:
'@aws-sdk/client-s3-control':
specifier: ^3.716.0
version: 3.716.0
'@aws-sdk/client-secrets-manager':
specifier: ^3.716.0
version: 3.716.0
'@aws-sdk/client-securityhub':
specifier: ^3.716.0
version: 3.716.0
'@aws-sdk/client-sns':
specifier: ^3.716.0
version: 3.716.0
'@aws-sdk/client-ssm':
specifier: ^3.716.0
version: 3.716.0
@ -222,6 +231,18 @@ packages:
resolution: {integrity: sha512-jzaH8IskAXVnqlZ3/H/ROwrB2HCnq/atlN7Hi7FIfjWvMPf5nfcJKfzJ1MXFX0EQR5qO6X4TbK7rgi7Bjw9NjQ==}
engines: {node: '>=16.0.0'}
'@aws-sdk/client-secrets-manager@3.716.0':
resolution: {integrity: sha512-j2JboOSR3PMoT5msr4uIMwkIm1owzkqgWI8i40IPDa1oeJXmZIx/xkCQq6Hxu5Ve1b2xtrw/8k1LN+TMCvuIfA==}
engines: {node: '>=16.0.0'}
'@aws-sdk/client-securityhub@3.716.0':
resolution: {integrity: sha512-/xGd2NQd7CtUDquRZvXbIDcMoBKaJmvwnjujVnmwth/6yC0gAVsBp6yeBSHOszI6L4mMXmSQ2iBglgnexpVlDQ==}
engines: {node: '>=16.0.0'}
'@aws-sdk/client-sns@3.716.0':
resolution: {integrity: sha512-Qg7EqlS83yiHRpfbhlyanRQ5aKmj1M8K7OJcDYgjI8FNf4YlS/YKJkv02SQVdW0lFLT2adceUXBjdAQXlbXC7g==}
engines: {node: '>=16.0.0'}
'@aws-sdk/client-ssm@3.716.0':
resolution: {integrity: sha512-da2wTUBCLGRoQf5Ahm/LuUIR/OkQ09kaX7yYRC2Vw+TOcMXbozJSzXbm99SXsOL4u8a8PRq+Vwfptc36e18Feg==}
engines: {node: '>=16.0.0'}
@ -2237,6 +2258,146 @@ snapshots:
transitivePeerDependencies:
- aws-crt
'@aws-sdk/client-secrets-manager@3.716.0':
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0)
'@aws-sdk/client-sts': 3.716.0
'@aws-sdk/core': 3.716.0
'@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0)
'@aws-sdk/middleware-host-header': 3.714.0
'@aws-sdk/middleware-logger': 3.714.0
'@aws-sdk/middleware-recursion-detection': 3.714.0
'@aws-sdk/middleware-user-agent': 3.716.0
'@aws-sdk/region-config-resolver': 3.714.0
'@aws-sdk/types': 3.714.0
'@aws-sdk/util-endpoints': 3.714.0
'@aws-sdk/util-user-agent-browser': 3.714.0
'@aws-sdk/util-user-agent-node': 3.716.0
'@smithy/config-resolver': 3.0.13
'@smithy/core': 2.5.6
'@smithy/fetch-http-handler': 4.1.2
'@smithy/hash-node': 3.0.11
'@smithy/invalid-dependency': 3.0.11
'@smithy/middleware-content-length': 3.0.13
'@smithy/middleware-endpoint': 3.2.6
'@smithy/middleware-retry': 3.0.31
'@smithy/middleware-serde': 3.0.11
'@smithy/middleware-stack': 3.0.11
'@smithy/node-config-provider': 3.1.12
'@smithy/node-http-handler': 3.3.3
'@smithy/protocol-http': 4.1.8
'@smithy/smithy-client': 3.5.1
'@smithy/types': 3.7.2
'@smithy/url-parser': 3.0.11
'@smithy/util-base64': 3.0.0
'@smithy/util-body-length-browser': 3.0.0
'@smithy/util-body-length-node': 3.0.0
'@smithy/util-defaults-mode-browser': 3.0.31
'@smithy/util-defaults-mode-node': 3.0.31
'@smithy/util-endpoints': 2.1.7
'@smithy/util-middleware': 3.0.11
'@smithy/util-retry': 3.0.11
'@smithy/util-utf8': 3.0.0
'@types/uuid': 9.0.8
tslib: 2.8.1
uuid: 9.0.1
transitivePeerDependencies:
- aws-crt
'@aws-sdk/client-securityhub@3.716.0':
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0)
'@aws-sdk/client-sts': 3.716.0
'@aws-sdk/core': 3.716.0
'@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0)
'@aws-sdk/middleware-host-header': 3.714.0
'@aws-sdk/middleware-logger': 3.714.0
'@aws-sdk/middleware-recursion-detection': 3.714.0
'@aws-sdk/middleware-user-agent': 3.716.0
'@aws-sdk/region-config-resolver': 3.714.0
'@aws-sdk/types': 3.714.0
'@aws-sdk/util-endpoints': 3.714.0
'@aws-sdk/util-user-agent-browser': 3.714.0
'@aws-sdk/util-user-agent-node': 3.716.0
'@smithy/config-resolver': 3.0.13
'@smithy/core': 2.5.6
'@smithy/fetch-http-handler': 4.1.2
'@smithy/hash-node': 3.0.11
'@smithy/invalid-dependency': 3.0.11
'@smithy/middleware-content-length': 3.0.13
'@smithy/middleware-endpoint': 3.2.6
'@smithy/middleware-retry': 3.0.31
'@smithy/middleware-serde': 3.0.11
'@smithy/middleware-stack': 3.0.11
'@smithy/node-config-provider': 3.1.12
'@smithy/node-http-handler': 3.3.3
'@smithy/protocol-http': 4.1.8
'@smithy/smithy-client': 3.5.1
'@smithy/types': 3.7.2
'@smithy/url-parser': 3.0.11
'@smithy/util-base64': 3.0.0
'@smithy/util-body-length-browser': 3.0.0
'@smithy/util-body-length-node': 3.0.0
'@smithy/util-defaults-mode-browser': 3.0.31
'@smithy/util-defaults-mode-node': 3.0.31
'@smithy/util-endpoints': 2.1.7
'@smithy/util-middleware': 3.0.11
'@smithy/util-retry': 3.0.11
'@smithy/util-utf8': 3.0.0
tslib: 2.8.1
transitivePeerDependencies:
- aws-crt
'@aws-sdk/client-sns@3.716.0':
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
'@aws-crypto/sha256-js': 5.2.0
'@aws-sdk/client-sso-oidc': 3.716.0(@aws-sdk/client-sts@3.716.0)
'@aws-sdk/client-sts': 3.716.0
'@aws-sdk/core': 3.716.0
'@aws-sdk/credential-provider-node': 3.716.0(@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0))(@aws-sdk/client-sts@3.716.0)
'@aws-sdk/middleware-host-header': 3.714.0
'@aws-sdk/middleware-logger': 3.714.0
'@aws-sdk/middleware-recursion-detection': 3.714.0
'@aws-sdk/middleware-user-agent': 3.716.0
'@aws-sdk/region-config-resolver': 3.714.0
'@aws-sdk/types': 3.714.0
'@aws-sdk/util-endpoints': 3.714.0
'@aws-sdk/util-user-agent-browser': 3.714.0
'@aws-sdk/util-user-agent-node': 3.716.0
'@smithy/config-resolver': 3.0.13
'@smithy/core': 2.5.6
'@smithy/fetch-http-handler': 4.1.2
'@smithy/hash-node': 3.0.11
'@smithy/invalid-dependency': 3.0.11
'@smithy/middleware-content-length': 3.0.13
'@smithy/middleware-endpoint': 3.2.6
'@smithy/middleware-retry': 3.0.31
'@smithy/middleware-serde': 3.0.11
'@smithy/middleware-stack': 3.0.11
'@smithy/node-config-provider': 3.1.12
'@smithy/node-http-handler': 3.3.3
'@smithy/protocol-http': 4.1.8
'@smithy/smithy-client': 3.5.1
'@smithy/types': 3.7.2
'@smithy/url-parser': 3.0.11
'@smithy/util-base64': 3.0.0
'@smithy/util-body-length-browser': 3.0.0
'@smithy/util-body-length-node': 3.0.0
'@smithy/util-defaults-mode-browser': 3.0.31
'@smithy/util-defaults-mode-node': 3.0.31
'@smithy/util-endpoints': 2.1.7
'@smithy/util-middleware': 3.0.11
'@smithy/util-retry': 3.0.11
'@smithy/util-utf8': 3.0.0
tslib: 2.8.1
transitivePeerDependencies:
- aws-crt
'@aws-sdk/client-ssm@3.716.0':
dependencies:
'@aws-crypto/sha256-browser': 5.2.0
@ -2703,7 +2864,7 @@ snapshots:
'@aws-sdk/core': 3.716.0
'@aws-sdk/types': 3.714.0
'@aws-sdk/util-endpoints': 3.714.0
'@smithy/core': 2.5.5
'@smithy/core': 2.5.6
'@smithy/protocol-http': 4.1.8
'@smithy/types': 3.7.2
tslib: 2.8.1

View File

@ -2,7 +2,15 @@ export interface BPSet {
check: () => Promise<{
compliantResources: string[]
nonCompliantResources: string[]
requiredParametersForFix: {name: string}[]
requiredParametersForFix: {
name: string
}[]
}>,
fix: (nonCompliantResources: string[], requiredParametersForFix: {name: string, value: string}[]) => Promise<void>
fix: (
nonCompliantResources: string[],
requiredParametersForFix: {
name: string,
value: string
}[]
) => Promise<void>
}

View File

@ -0,0 +1,48 @@
import {
SecretsManagerClient,
ListSecretsCommand,
RotateSecretCommand,
UpdateSecretCommand
} from '@aws-sdk/client-secrets-manager'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class SecretsManagerRotationEnabledCheck implements BPSet {
private readonly client = new SecretsManagerClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getSecrets = async () => {
const response = await this.memoClient.send(new ListSecretsCommand({}))
return response.SecretList || []
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const secrets = await this.getSecrets()
for (const secret of secrets) {
if (secret.RotationEnabled) {
compliantResources.push(secret.ARN!)
} else {
nonCompliantResources.push(secret.ARN!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
await this.client.send(
new RotateSecretCommand({
SecretId: arn
})
)
}
}
}

View File

@ -0,0 +1,55 @@
import {
SecretsManagerClient,
ListSecretsCommand,
RotateSecretCommand
} from '@aws-sdk/client-secrets-manager'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class SecretsManagerScheduledRotationSuccessCheck implements BPSet {
private readonly client = new SecretsManagerClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getSecrets = async () => {
const response = await this.memoClient.send(new ListSecretsCommand({}))
return response.SecretList || []
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const secrets = await this.getSecrets()
for (const secret of secrets) {
if (secret.RotationEnabled) {
const now = new Date()
const lastRotated = secret.LastRotatedDate ? new Date(secret.LastRotatedDate) : undefined
const rotationPeriod = secret.RotationRules?.AutomaticallyAfterDays
? secret.RotationRules.AutomaticallyAfterDays + 2
: undefined
if (!lastRotated || !rotationPeriod || now.getTime() - lastRotated.getTime() > rotationPeriod * 24 * 60 * 60 * 1000) {
nonCompliantResources.push(secret.ARN!)
} else {
compliantResources.push(secret.ARN!)
}
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
await this.client.send(
new RotateSecretCommand({
SecretId: arn
})
)
}
}
}

View File

@ -0,0 +1,52 @@
import {
SecretsManagerClient,
ListSecretsCommand,
RotateSecretCommand
} from '@aws-sdk/client-secrets-manager'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class SecretsManagerSecretPeriodicRotation implements BPSet {
private readonly client = new SecretsManagerClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getSecrets = async () => {
const response = await this.memoClient.send(new ListSecretsCommand({}))
return response.SecretList || []
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const secrets = await this.getSecrets()
for (const secret of secrets) {
if (secret.RotationEnabled) {
const now = new Date()
const lastRotated = secret.LastRotatedDate ? new Date(secret.LastRotatedDate) : undefined
if (!lastRotated || now.getTime() - lastRotated.getTime() > 90 * 24 * 60 * 60 * 1000) {
nonCompliantResources.push(secret.ARN!)
} else {
compliantResources.push(secret.ARN!)
}
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
await this.client.send(
new RotateSecretCommand({
SecretId: arn
})
)
}
}
}

View File

@ -0,0 +1,51 @@
import {
SecurityHubClient,
DescribeHubCommand,
EnableSecurityHubCommand
} from '@aws-sdk/client-securityhub'
import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class SecurityHubEnabled implements BPSet {
private readonly securityHubClient = new SecurityHubClient({})
private readonly stsClient = new STSClient({})
private readonly memoSecurityHubClient = Memorizer.memo(this.securityHubClient)
private readonly memoStsClient = Memorizer.memo(this.stsClient)
private readonly getAWSAccountId = async () => {
const response = await this.memoStsClient.send(new GetCallerIdentityCommand({}))
return response.Account!
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const awsAccountId = await this.getAWSAccountId()
try {
await this.memoSecurityHubClient.send(new DescribeHubCommand({}))
compliantResources.push(awsAccountId)
} catch (error: any) {
if (error.name === 'InvalidAccessException') {
nonCompliantResources.push(awsAccountId)
} else {
throw error
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const accountId of nonCompliantResources) {
if (accountId) {
await this.securityHubClient.send(new EnableSecurityHubCommand({}))
}
}
}
}

View File

@ -0,0 +1,69 @@
import {
SNSClient,
ListTopicsCommand,
GetTopicAttributesCommand,
SetTopicAttributesCommand
} from '@aws-sdk/client-sns'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class SNSEncryptedKMS implements BPSet {
private readonly client = new SNSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getTopics = async () => {
const topicsResponse = await this.memoClient.send(new ListTopicsCommand({}))
const topics = topicsResponse.Topics || []
const topicDetails = []
for (const topic of topics) {
const attributes = await this.memoClient.send(
new GetTopicAttributesCommand({ TopicArn: topic.TopicArn! })
)
topicDetails.push({ ...attributes.Attributes, TopicArn: topic.TopicArn! })
}
return topicDetails
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const topics = await this.getTopics() as any
for (const topic of topics) {
if (topic.KmsMasterKeyId) {
compliantResources.push(topic.TopicArn!)
} else {
nonCompliantResources.push(topic.TopicArn!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: [{ name: 'kms-key-id', value: '<KMS_KEY_ID>' }]
}
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const kmsKeyId = requiredParametersForFix.find(param => param.name === 'kms-key-id')?.value
if (!kmsKeyId) {
throw new Error("Required parameter 'kms-key-id' is missing.")
}
for (const arn of nonCompliantResources) {
await this.client.send(
new SetTopicAttributesCommand({
TopicArn: arn,
AttributeName: 'KmsMasterKeyId',
AttributeValue: kmsKeyId
})
)
}
}
}

View File

@ -0,0 +1,79 @@
import {
SNSClient,
ListTopicsCommand,
GetTopicAttributesCommand,
SetTopicAttributesCommand
} from '@aws-sdk/client-sns'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class SNSTopicMessageDeliveryNotificationEnabled implements BPSet {
private readonly client = new SNSClient({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getTopics = async () => {
const topicsResponse = await this.memoClient.send(new ListTopicsCommand({}))
const topics = topicsResponse.Topics || []
const topicDetails = []
for (const topic of topics) {
const attributes = await this.memoClient.send(
new GetTopicAttributesCommand({ TopicArn: topic.TopicArn! })
)
topicDetails.push({ ...attributes.Attributes, TopicArn: topic.TopicArn! })
}
return topicDetails
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const topics = await this.getTopics()
for (const topic of topics) {
const feedbackRoles = Object.keys(topic).filter(key => key.endsWith('FeedbackRoleArn'))
if (feedbackRoles.length > 0) {
compliantResources.push(topic.TopicArn!)
} else {
nonCompliantResources.push(topic.TopicArn!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: [
{ name: 'sns-feedback-role-arn', value: '<FEEDBACK_ROLE_ARN>' }
]
}
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const feedbackRoleArn = requiredParametersForFix.find(
param => param.name === 'sns-feedback-role-arn'
)?.value
if (!feedbackRoleArn) {
throw new Error("Required parameter 'sns-feedback-role-arn' is missing.")
}
for (const arn of nonCompliantResources) {
await this.client.send(
new SetTopicAttributesCommand({
TopicArn: arn,
AttributeName: 'DeliveryPolicy',
AttributeValue: JSON.stringify({
http: {
DefaultFeedbackRoleArn: feedbackRoleArn
}
})
})
)
}
}
}

View File

@ -0,0 +1,49 @@
import {
EC2Client,
DescribeTransitGatewaysCommand,
ModifyTransitGatewayCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class EC2TransitGatewayAutoVPCAttachDisabled implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const response = await this.memoClient.send(new DescribeTransitGatewaysCommand({}))
const transitGateways = response.TransitGateways || []
for (const gateway of transitGateways) {
if (gateway.Options?.AutoAcceptSharedAttachments === 'enable') {
nonCompliantResources.push(gateway.TransitGatewayArn!)
} else {
compliantResources.push(gateway.TransitGatewayArn!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const transitGatewayId = arn.split(':transit-gateway/')[1]
await this.client.send(
new ModifyTransitGatewayCommand({
TransitGatewayId: transitGatewayId,
Options: {
AutoAcceptSharedAttachments: 'disable'
}
})
)
}
}
}

View File

@ -0,0 +1,57 @@
import {
EC2Client,
DescribeSecurityGroupRulesCommand,
RevokeSecurityGroupIngressCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class RestrictedCommonPorts implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getSecurityGroupRules = async () => {
const response = await this.memoClient.send(new DescribeSecurityGroupRulesCommand({}))
return response.SecurityGroupRules || []
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const commonPorts = [-1, 22, 80, 3306, 3389, 5432, 6379, 11211]
const rules = await this.getSecurityGroupRules()
for (const rule of rules) {
if (
!rule.IsEgress &&
commonPorts.includes(rule.FromPort!) &&
commonPorts.includes(rule.ToPort!) &&
!rule.PrefixListId
) {
nonCompliantResources.push(`${rule.GroupId} / ${rule.SecurityGroupRuleId}`)
} else {
compliantResources.push(`${rule.GroupId} / ${rule.SecurityGroupRuleId}`)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const resource of nonCompliantResources) {
const [groupId, ruleId] = resource.split(' / ')
await this.client.send(
new RevokeSecurityGroupIngressCommand({
GroupId: groupId,
SecurityGroupRuleIds: [ruleId]
})
)
}
}
}

View File

@ -0,0 +1,55 @@
import {
EC2Client,
DescribeSecurityGroupRulesCommand,
RevokeSecurityGroupIngressCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class RestrictedSSH implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getSecurityGroupRules = async () => {
const response = await this.memoClient.send(new DescribeSecurityGroupRulesCommand({}))
return response.SecurityGroupRules || []
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const rules = await this.getSecurityGroupRules()
for (const rule of rules) {
if (
!rule.IsEgress &&
rule.FromPort! <= 22 &&
rule.ToPort! >= 22 &&
rule.CidrIpv4 === '0.0.0.0/0'
) {
nonCompliantResources.push(`${rule.GroupId} / ${rule.SecurityGroupRuleId}`)
} else {
compliantResources.push(`${rule.GroupId} / ${rule.SecurityGroupRuleId}`)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const resource of nonCompliantResources) {
const [groupId, ruleId] = resource.split(' / ')
await this.client.send(
new RevokeSecurityGroupIngressCommand({
GroupId: groupId,
SecurityGroupRuleIds: [ruleId]
})
)
}
}
}

View File

@ -0,0 +1,45 @@
import {
EC2Client,
DescribeSubnetsCommand,
ModifySubnetAttributeCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class SubnetAutoAssignPublicIPDisabled implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const response = await this.memoClient.send(new DescribeSubnetsCommand({}))
const subnets = response.Subnets || []
for (const subnet of subnets) {
if (subnet.MapPublicIpOnLaunch) {
nonCompliantResources.push(subnet.SubnetId!)
} else {
compliantResources.push(subnet.SubnetId!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const subnetId of nonCompliantResources) {
await this.client.send(
new ModifySubnetAttributeCommand({
SubnetId: subnetId,
MapPublicIpOnLaunch: { Value: false }
})
)
}
}
}

View File

@ -0,0 +1,56 @@
import {
EC2Client,
DescribeSecurityGroupsCommand,
RevokeSecurityGroupIngressCommand,
RevokeSecurityGroupEgressCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class VPCDefaultSecurityGroupClosed implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const response = await this.memoClient.send(
new DescribeSecurityGroupsCommand({
Filters: [{ Name: 'group-name', Values: ['default'] }]
})
)
const securityGroups = response.SecurityGroups || []
for (const group of securityGroups) {
if (group.IpPermissions?.length || group.IpPermissionsEgress?.length) {
nonCompliantResources.push(group.GroupId!)
} else {
compliantResources.push(group.GroupId!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const groupId of nonCompliantResources) {
await this.client.send(
new RevokeSecurityGroupIngressCommand({
GroupId: groupId,
IpPermissions: []
})
)
await this.client.send(
new RevokeSecurityGroupEgressCommand({
GroupId: groupId,
IpPermissions: []
})
)
}
}
}

View File

@ -0,0 +1,66 @@
import {
EC2Client,
DescribeVpcsCommand,
DescribeFlowLogsCommand,
CreateFlowLogsCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class VPCFlowLogsEnabled implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const flowLogsResponse = await this.memoClient.send(new DescribeFlowLogsCommand({}))
const flowLogs = flowLogsResponse.FlowLogs || []
const flowLogEnabledVpcs = flowLogs.map(log => log.ResourceId!)
const vpcsResponse = await this.memoClient.send(new DescribeVpcsCommand({}))
const vpcs = vpcsResponse.Vpcs || []
for (const vpc of vpcs) {
if (flowLogEnabledVpcs.includes(vpc.VpcId!)) {
compliantResources.push(vpc.VpcId!)
} else {
nonCompliantResources.push(vpc.VpcId!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: [
{ name: 'log-group-name', value: '<LOG_GROUP_NAME>' },
{ name: 'iam-role-arn', value: '<IAM_ROLE_ARN>' }
]
}
}
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const logGroupName = requiredParametersForFix.find(param => param.name === 'log-group-name')?.value
const iamRoleArn = requiredParametersForFix.find(param => param.name === 'iam-role-arn')?.value
if (!logGroupName || !iamRoleArn) {
throw new Error("Required parameters 'log-group-name' and 'iam-role-arn' are missing.")
}
for (const vpcId of nonCompliantResources) {
await this.client.send(
new CreateFlowLogsCommand({
ResourceIds: [vpcId],
ResourceType: 'VPC',
LogGroupName: logGroupName,
DeliverLogsPermissionArn: iamRoleArn,
TrafficType: 'ALL'
})
)
}
}
}

View File

@ -0,0 +1,44 @@
import {
EC2Client,
DescribeNetworkAclsCommand,
DeleteNetworkAclCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class VPCNetworkACLUnusedCheck implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const response = await this.memoClient.send(new DescribeNetworkAclsCommand({}))
const networkAcls = response.NetworkAcls || []
for (const acl of networkAcls) {
if (!acl.Associations || acl.Associations.length === 0) {
nonCompliantResources.push(acl.NetworkAclId!)
} else {
compliantResources.push(acl.NetworkAclId!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const aclId of nonCompliantResources) {
await this.client.send(
new DeleteNetworkAclCommand({
NetworkAclId: aclId
})
)
}
}
}

View File

@ -0,0 +1,56 @@
import {
EC2Client,
DescribeVpcPeeringConnectionsCommand,
ModifyVpcPeeringConnectionOptionsCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class VPCPeeringDNSResolutionCheck implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const response = await this.memoClient.send(new DescribeVpcPeeringConnectionsCommand({}))
const vpcPeeringConnections = response.VpcPeeringConnections || []
for (const connection of vpcPeeringConnections) {
const accepterOptions = connection.AccepterVpcInfo?.PeeringOptions
const requesterOptions = connection.RequesterVpcInfo?.PeeringOptions
if (
!accepterOptions?.AllowDnsResolutionFromRemoteVpc ||
!requesterOptions?.AllowDnsResolutionFromRemoteVpc
) {
nonCompliantResources.push(connection.VpcPeeringConnectionId!)
} else {
compliantResources.push(connection.VpcPeeringConnectionId!)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const connectionId of nonCompliantResources) {
await this.client.send(
new ModifyVpcPeeringConnectionOptionsCommand({
VpcPeeringConnectionId: connectionId,
AccepterPeeringConnectionOptions: {
AllowDnsResolutionFromRemoteVpc: true
},
RequesterPeeringConnectionOptions: {
AllowDnsResolutionFromRemoteVpc: true
}
})
)
}
}
}

View File

@ -0,0 +1,56 @@
import {
EC2Client,
DescribeSecurityGroupRulesCommand,
RevokeSecurityGroupIngressCommand
} from '@aws-sdk/client-ec2'
import { BPSet } from '../BPSet'
import { Memorizer } from '../../Memorizer'
export class VPCSGOpenOnlyToAuthorizedPorts implements BPSet {
private readonly client = new EC2Client({})
private readonly memoClient = Memorizer.memo(this.client)
private readonly getSecurityGroupRules = async () => {
const response = await this.memoClient.send(new DescribeSecurityGroupRulesCommand({}))
return response.SecurityGroupRules || []
}
public readonly check = async () => {
const compliantResources: string[] = []
const nonCompliantResources: string[] = []
const authorizedPorts = [80, 443] // Example authorized ports
const rules = await this.getSecurityGroupRules()
for (const rule of rules) {
if (
!rule.IsEgress &&
(rule.CidrIpv4 === '0.0.0.0/0' || rule.CidrIpv6 === '::/0') &&
!authorizedPorts.includes(rule.FromPort!) &&
!authorizedPorts.includes(rule.ToPort!)
) {
nonCompliantResources.push(`${rule.GroupId} / ${rule.SecurityGroupRuleId}`)
} else {
compliantResources.push(`${rule.GroupId} / ${rule.SecurityGroupRuleId}`)
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: []
}
}
public readonly fix = async (nonCompliantResources: string[]) => {
for (const resource of nonCompliantResources) {
const [groupId, ruleId] = resource.split(' / ')
await this.client.send(
new RevokeSecurityGroupIngressCommand({
GroupId: groupId,
SecurityGroupRuleIds: [ruleId]
})
)
}
}
}

View File

@ -0,0 +1,74 @@
import {
WAFV2Client,
ListWebACLsCommand,
GetLoggingConfigurationCommand,
PutLoggingConfigurationCommand,
} from '@aws-sdk/client-wafv2';
import { BPSet } from '../BPSet';
import { Memorizer } from '../../Memorizer';
export class WAFv2LoggingEnabled implements BPSet {
private readonly regionalClient = new WAFV2Client({});
private readonly globalClient = new WAFV2Client({ region: 'us-east-1' });
private readonly memoRegionalClient = Memorizer.memo(this.regionalClient);
private readonly memoGlobalClient = Memorizer.memo(this.globalClient);
private readonly getWebACLs = async (scope: 'REGIONAL' | 'CLOUDFRONT') => {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const response = await client.send(new ListWebACLsCommand({ Scope: scope }));
return response.WebACLs || [];
};
public readonly check = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
for (const scope of ['REGIONAL', 'CLOUDFRONT'] as const) {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const webACLs = await this.getWebACLs(scope);
for (const webACL of webACLs) {
try {
await client.send(new GetLoggingConfigurationCommand({ ResourceArn: webACL.ARN }));
compliantResources.push(webACL.ARN!);
} catch (error: any) {
if (error.name === 'WAFNonexistentItemException') {
nonCompliantResources.push(webACL.ARN!);
} else {
throw error;
}
}
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: [{ name: 'log-group-arn', value: '<LOG_GROUP_ARN>' }],
};
};
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const logGroupArn = requiredParametersForFix.find(param => param.name === 'log-group-arn')?.value;
if (!logGroupArn) {
throw new Error("Required parameter 'log-group-arn' is missing.");
}
for (const arn of nonCompliantResources) {
const client = arn.includes('global') ? this.globalClient : this.regionalClient;
await client.send(
new PutLoggingConfigurationCommand({
LoggingConfiguration: {
ResourceArn: arn,
LogDestinationConfigs: [logGroupArn],
},
})
);
}
};
}

View File

@ -0,0 +1,71 @@
import {
WAFV2Client,
ListRuleGroupsCommand,
GetRuleGroupCommand,
UpdateRuleGroupCommand,
} from '@aws-sdk/client-wafv2';
import { BPSet } from '../BPSet';
import { Memorizer } from '../../Memorizer';
export class WAFv2RuleGroupLoggingEnabled implements BPSet {
private readonly regionalClient = new WAFV2Client({});
private readonly globalClient = new WAFV2Client({ region: 'us-east-1' });
private readonly memoRegionalClient = Memorizer.memo(this.regionalClient);
private readonly memoGlobalClient = Memorizer.memo(this.globalClient);
private readonly getRuleGroups = async (scope: 'REGIONAL' | 'CLOUDFRONT') => {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const response = await client.send(new ListRuleGroupsCommand({ Scope: scope }));
return response.RuleGroups || [];
};
public readonly check = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
for (const scope of ['REGIONAL', 'CLOUDFRONT'] as const) {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const ruleGroups = await this.getRuleGroups(scope);
for (const ruleGroup of ruleGroups) {
const details = await client.send(
new GetRuleGroupCommand({ Name: ruleGroup.Name!, Id: ruleGroup.Id!, Scope: scope })
);
if (details.RuleGroup?.VisibilityConfig?.CloudWatchMetricsEnabled) {
compliantResources.push(ruleGroup.ARN!);
} else {
nonCompliantResources.push(ruleGroup.ARN!);
}
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: [],
};
};
public readonly fix = async (nonCompliantResources: string[]) => {
for (const arn of nonCompliantResources) {
const client = arn.includes('global') ? this.globalClient : this.regionalClient;
const [name, id] = arn.split('/')[1].split(':');
await client.send(
new UpdateRuleGroupCommand({
Name: name,
Id: id,
Scope: arn.includes('global') ? 'CLOUDFRONT' : 'REGIONAL',
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: `WAFRuleGroup-${name}`,
SampledRequestsEnabled: true,
},
LockToken: undefined
})
);
}
};
}

View File

@ -0,0 +1,93 @@
import {
WAFV2Client,
ListRuleGroupsCommand,
GetRuleGroupCommand,
UpdateRuleGroupCommand
} from '@aws-sdk/client-wafv2';
import { BPSet } from '../BPSet';
import { Memorizer } from '../../Memorizer';
export class WAFv2RuleGroupNotEmpty implements BPSet {
private readonly regionalClient = new WAFV2Client({});
private readonly globalClient = new WAFV2Client({ region: 'us-east-1' });
private readonly memoRegionalClient = Memorizer.memo(this.regionalClient);
private readonly memoGlobalClient = Memorizer.memo(this.globalClient);
private readonly getRuleGroups = async (scope: 'REGIONAL' | 'CLOUDFRONT') => {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const response = await client.send(new ListRuleGroupsCommand({ Scope: scope }));
return response.RuleGroups || [];
};
public readonly check = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
for (const scope of ['REGIONAL', 'CLOUDFRONT'] as const) {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const ruleGroups = await this.getRuleGroups(scope);
for (const ruleGroup of ruleGroups) {
const details = await client.send(
new GetRuleGroupCommand({ Name: ruleGroup.Name!, Id: ruleGroup.Id!, Scope: scope })
);
if (details.RuleGroup?.Rules?.length! > 0) {
compliantResources.push(ruleGroup.ARN!);
} else {
nonCompliantResources.push(ruleGroup.ARN!);
}
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: [{ name: 'default-rule', value: '<DEFAULT_RULE>' }]
};
};
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const defaultRule = requiredParametersForFix.find(param => param.name === 'default-rule')?.value;
if (!defaultRule) {
throw new Error("Required parameter 'default-rule' is missing.");
}
for (const arn of nonCompliantResources) {
const client = arn.includes('global') ? this.globalClient : this.regionalClient;
const [name, id] = arn.split('/')[1].split(':');
await client.send(
new UpdateRuleGroupCommand({
Name: name,
Id: id,
Scope: arn.includes('global') ? 'CLOUDFRONT' : 'REGIONAL',
LockToken: undefined,
Rules: [
{
Name: 'DefaultRule',
Priority: 1,
Action: { Allow: {} },
Statement: JSON.parse(defaultRule),
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: `DefaultRule-${name}`,
SampledRequestsEnabled: true
}
}
],
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: `RuleGroup-${name}`,
SampledRequestsEnabled: true
}
})
);
}
};
}

View File

@ -0,0 +1,94 @@
import {
WAFV2Client,
ListWebACLsCommand,
GetWebACLCommand,
UpdateWebACLCommand
} from '@aws-sdk/client-wafv2';
import { BPSet } from '../BPSet';
import { Memorizer } from '../../Memorizer';
export class WAFv2WebACLNotEmpty implements BPSet {
private readonly regionalClient = new WAFV2Client({});
private readonly globalClient = new WAFV2Client({ region: 'us-east-1' });
private readonly memoRegionalClient = Memorizer.memo(this.regionalClient);
private readonly memoGlobalClient = Memorizer.memo(this.globalClient);
private readonly getWebACLs = async (scope: 'REGIONAL' | 'CLOUDFRONT') => {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const response = await client.send(new ListWebACLsCommand({ Scope: scope }));
return response.WebACLs || [];
};
public readonly check = async () => {
const compliantResources: string[] = [];
const nonCompliantResources: string[] = [];
for (const scope of ['REGIONAL', 'CLOUDFRONT'] as const) {
const client = scope === 'REGIONAL' ? this.memoRegionalClient : this.memoGlobalClient;
const webACLs = await this.getWebACLs(scope);
for (const webACL of webACLs) {
const details = await client.send(
new GetWebACLCommand({ Name: webACL.Name!, Id: webACL.Id!, Scope: scope })
);
if (details.WebACL?.Rules?.length! > 0) {
compliantResources.push(webACL.ARN!);
} else {
nonCompliantResources.push(webACL.ARN!);
}
}
}
return {
compliantResources,
nonCompliantResources,
requiredParametersForFix: [{ name: 'default-rule', value: '<DEFAULT_RULE>' }]
};
};
public readonly fix = async (
nonCompliantResources: string[],
requiredParametersForFix: { name: string; value: string }[]
) => {
const defaultRule = requiredParametersForFix.find(param => param.name === 'default-rule')?.value;
if (!defaultRule) {
throw new Error("Required parameter 'default-rule' is missing.");
}
for (const arn of nonCompliantResources) {
const client = arn.includes('global') ? this.globalClient : this.regionalClient;
const [name, id] = arn.split('/')[1].split(':');
await client.send(
new UpdateWebACLCommand({
Name: name,
Id: id,
Scope: arn.includes('global') ? 'CLOUDFRONT' : 'REGIONAL',
LockToken: undefined,
Rules: [
{
Name: 'DefaultRule',
Priority: 1,
Action: { Allow: {} },
Statement: JSON.parse(defaultRule),
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: `DefaultRule-${name}`,
SampledRequestsEnabled: true
}
}
],
DefaultAction: { Allow: {} },
VisibilityConfig: {
CloudWatchMetricsEnabled: true,
MetricName: `WebACL-${name}`,
SampledRequestsEnabled: true
}
})
);
}
};
}