From e845d4a9c31a1f1515e478fa74c2808244b120da Mon Sep 17 00:00:00 2001 From: Minhyeok Park Date: Tue, 24 Dec 2024 09:19:06 +0900 Subject: [PATCH] feat: add more bps --- package.json | 15 + pnpm-lock.yaml | 965 ++++++++++++++++++ .../CWLogGroupRetentionPeriodCheck.ts | 60 ++ .../CloudWatchAlarmSettingsCheck.ts | 85 ++ ...eBuildProjectEnvironmentPrivilegedCheck.ts | 66 ++ .../CodeBuildProjectLoggingEnabled.ts | 74 ++ .../CodeDeployAutoRollbackMonitorEnabled.ts | 88 ++ .../dynamodb/DynamoDBAutoscalingEnabled.ts | 133 +++ .../DynamoDBLastBackupRecoveryPointCreated.ts | 78 ++ src/bpsets/dynamodb/DynamoDBPITREnabled.ts | 70 ++ .../DynamoDBTableDeletionProtectionEnabled.ts | 58 ++ .../dynamodb/DynamoDBTableEncryptedKMS.ts | 74 ++ .../DynamoDBTableEncryptionEnabled.ts | 60 ++ src/bpsets/ec2/EC2EbsEncryptionByDefault.ts | 36 + src/bpsets/ec2/EC2Imdsv2Check.ts | 45 + .../EC2InstanceDetailedMonitoringEnabled.ts | 42 + .../ec2/EC2InstanceManagedBySystemsManager.ts | 48 + src/bpsets/ec2/EC2InstanceProfileAttached,ts | 56 + src/bpsets/ec2/EC2NoAmazonKeyPair.ts | 39 + src/bpsets/ec2/EC2StoppedInstance.ts | 46 + src/bpsets/ec2/EC2TokenHopLimitCheck.ts | 48 + src/bpsets/ecr/ECRKmsEncryption1.ts | 99 ++ .../ecr/ECRPrivateImageScanningEnabled.ts | 50 + .../ECRPrivateLifecyclePolicyConfigured.ts | 72 ++ .../ecr/ECRPrivateTagImmutabilityEnabled.ts | 50 + src/bpsets/ecs/ECSAwsVpcNetworkingEnabled.ts | 67 ++ src/bpsets/ecs/ECSContainerInsightsEnabled.ts | 51 + src/bpsets/ecs/ECSContainersNonPrivileged.ts | 77 ++ src/bpsets/ecs/ECSContainersReadonlyAccess.ts | 77 ++ .../ecs/ECSFargateLatestPlatformVersion.ts | 70 ++ .../ecs/ECSTaskDefinitionLogConfiguration.ts | 88 ++ .../ecs/ECSTaskDefinitionMemoryHardLimit.ts | 77 ++ .../ecs/ECSTaskDefinitionNonRootUser.ts | 77 ++ .../efs/EFSAccessPointEnforceRootDirectory.ts | 71 ++ .../efs/EFSAccessPointEnforceUserIdentity.ts | 69 ++ src/bpsets/efs/EFSAutomaticBackupsEnabled.ts | 55 + src/bpsets/efs/EFSEncryptedCheck.ts | 64 ++ .../efs/EFSMountTargetPublicAccessible.ts | 71 ++ src/bpsets/eks/EKSClusterLoggingEnabled.ts | 67 ++ src/bpsets/eks/EKSClusterSecretsEncrypted.ts | 74 ++ src/bpsets/eks/EKSEndpointNoPublicAccess.ts | 62 ++ ...ElastiCacheAutoMinorVersionUpgradeCheck.ts | 49 + ...tiCacheRedisClusterAutomaticBackupCheck.ts | 60 ++ .../ElastiCacheReplGrpAutoFailoverEnabled.ts | 49 + .../ElastiCacheReplGrpEncryptedAtRest.ts | 43 + .../ElastiCacheReplGrpEncryptedInTransit.ts | 43 + .../ElastiCacheSubnetGroupCheck.ts | 84 ++ .../IAMPolicyNoStatementsWithAdminAccess.ts | 67 ++ .../IAMPolicyNoStatementsWithFullAccess.ts | 67 ++ src/bpsets/iam/IAMRoleManagedPolicyCheck.ts | 56 + .../AuroraLastBackupRecoveryPointCreated.ts | 80 ++ .../rds/AuroraMySQLBacktrackingEnabled.ts | 52 + src/bpsets/rds/DBInstanceBackupEnabled.ts | 60 ++ ...DSClusterAutoMinorVersionUpgradeEnabled.ts | 50 + src/bpsets/rds/RDSClusterDefaultAdminCheck.ts | 67 ++ .../RDSClusterDeletionProtectionEnabled.ts | 50 + src/bpsets/rds/RDSClusterEncryptedAtRest.ts | 43 + .../rds/RDSClusterIAMAuthenticationEnabled.ts | 53 + src/bpsets/rds/RDSClusterMultiAZEnabled.ts | 43 + .../rds/RDSDBSecurityGroupNotAllowed.ts | 64 ++ .../rds/RDSEnhancedMonitoringEnabled.ts | 61 ++ .../rds/RDSInstancePublicAccessCheck.ts | 50 + src/bpsets/rds/RDSLoggingEnabled.ts | 70 ++ src/bpsets/rds/RDSSnapshotEncrypted.ts | 62 ++ 64 files changed, 4897 insertions(+) create mode 100644 src/bpsets/cloudwatch/CWLogGroupRetentionPeriodCheck.ts create mode 100644 src/bpsets/cloudwatch/CloudWatchAlarmSettingsCheck.ts create mode 100644 src/bpsets/codeseries/CodeBuildProjectEnvironmentPrivilegedCheck.ts create mode 100644 src/bpsets/codeseries/CodeBuildProjectLoggingEnabled.ts create mode 100644 src/bpsets/codeseries/CodeDeployAutoRollbackMonitorEnabled.ts create mode 100644 src/bpsets/dynamodb/DynamoDBAutoscalingEnabled.ts create mode 100644 src/bpsets/dynamodb/DynamoDBLastBackupRecoveryPointCreated.ts create mode 100644 src/bpsets/dynamodb/DynamoDBPITREnabled.ts create mode 100644 src/bpsets/dynamodb/DynamoDBTableDeletionProtectionEnabled.ts create mode 100644 src/bpsets/dynamodb/DynamoDBTableEncryptedKMS.ts create mode 100644 src/bpsets/dynamodb/DynamoDBTableEncryptionEnabled.ts create mode 100644 src/bpsets/ec2/EC2EbsEncryptionByDefault.ts create mode 100644 src/bpsets/ec2/EC2Imdsv2Check.ts create mode 100644 src/bpsets/ec2/EC2InstanceDetailedMonitoringEnabled.ts create mode 100644 src/bpsets/ec2/EC2InstanceManagedBySystemsManager.ts create mode 100644 src/bpsets/ec2/EC2InstanceProfileAttached,ts create mode 100644 src/bpsets/ec2/EC2NoAmazonKeyPair.ts create mode 100644 src/bpsets/ec2/EC2StoppedInstance.ts create mode 100644 src/bpsets/ec2/EC2TokenHopLimitCheck.ts create mode 100644 src/bpsets/ecr/ECRKmsEncryption1.ts create mode 100644 src/bpsets/ecr/ECRPrivateImageScanningEnabled.ts create mode 100644 src/bpsets/ecr/ECRPrivateLifecyclePolicyConfigured.ts create mode 100644 src/bpsets/ecr/ECRPrivateTagImmutabilityEnabled.ts create mode 100644 src/bpsets/ecs/ECSAwsVpcNetworkingEnabled.ts create mode 100644 src/bpsets/ecs/ECSContainerInsightsEnabled.ts create mode 100644 src/bpsets/ecs/ECSContainersNonPrivileged.ts create mode 100644 src/bpsets/ecs/ECSContainersReadonlyAccess.ts create mode 100644 src/bpsets/ecs/ECSFargateLatestPlatformVersion.ts create mode 100644 src/bpsets/ecs/ECSTaskDefinitionLogConfiguration.ts create mode 100644 src/bpsets/ecs/ECSTaskDefinitionMemoryHardLimit.ts create mode 100644 src/bpsets/ecs/ECSTaskDefinitionNonRootUser.ts create mode 100644 src/bpsets/efs/EFSAccessPointEnforceRootDirectory.ts create mode 100644 src/bpsets/efs/EFSAccessPointEnforceUserIdentity.ts create mode 100644 src/bpsets/efs/EFSAutomaticBackupsEnabled.ts create mode 100644 src/bpsets/efs/EFSEncryptedCheck.ts create mode 100644 src/bpsets/efs/EFSMountTargetPublicAccessible.ts create mode 100644 src/bpsets/eks/EKSClusterLoggingEnabled.ts create mode 100644 src/bpsets/eks/EKSClusterSecretsEncrypted.ts create mode 100644 src/bpsets/eks/EKSEndpointNoPublicAccess.ts create mode 100644 src/bpsets/elasticache/ElastiCacheAutoMinorVersionUpgradeCheck.ts create mode 100644 src/bpsets/elasticache/ElastiCacheRedisClusterAutomaticBackupCheck.ts create mode 100644 src/bpsets/elasticache/ElastiCacheReplGrpAutoFailoverEnabled.ts create mode 100644 src/bpsets/elasticache/ElastiCacheReplGrpEncryptedAtRest.ts create mode 100644 src/bpsets/elasticache/ElastiCacheReplGrpEncryptedInTransit.ts create mode 100644 src/bpsets/elasticache/ElastiCacheSubnetGroupCheck.ts create mode 100644 src/bpsets/iam/IAMPolicyNoStatementsWithAdminAccess.ts create mode 100644 src/bpsets/iam/IAMPolicyNoStatementsWithFullAccess.ts create mode 100644 src/bpsets/iam/IAMRoleManagedPolicyCheck.ts create mode 100644 src/bpsets/rds/AuroraLastBackupRecoveryPointCreated.ts create mode 100644 src/bpsets/rds/AuroraMySQLBacktrackingEnabled.ts create mode 100644 src/bpsets/rds/DBInstanceBackupEnabled.ts create mode 100644 src/bpsets/rds/RDSClusterAutoMinorVersionUpgradeEnabled.ts create mode 100644 src/bpsets/rds/RDSClusterDefaultAdminCheck.ts create mode 100644 src/bpsets/rds/RDSClusterDeletionProtectionEnabled.ts create mode 100644 src/bpsets/rds/RDSClusterEncryptedAtRest.ts create mode 100644 src/bpsets/rds/RDSClusterIAMAuthenticationEnabled.ts create mode 100644 src/bpsets/rds/RDSClusterMultiAZEnabled.ts create mode 100644 src/bpsets/rds/RDSDBSecurityGroupNotAllowed.ts create mode 100644 src/bpsets/rds/RDSEnhancedMonitoringEnabled.ts create mode 100644 src/bpsets/rds/RDSInstancePublicAccessCheck.ts create mode 100644 src/bpsets/rds/RDSLoggingEnabled.ts create mode 100644 src/bpsets/rds/RDSSnapshotEncrypted.ts diff --git a/package.json b/package.json index 85c68cc..9c80924 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,28 @@ "license": "MIT", "dependencies": { "@aws-sdk/client-apigatewayv2": "^3.716.0", + "@aws-sdk/client-application-auto-scaling": "^3.716.0", "@aws-sdk/client-auto-scaling": "^3.716.0", "@aws-sdk/client-backup": "^3.716.0", "@aws-sdk/client-cloudfront": "^3.716.0", + "@aws-sdk/client-cloudwatch": "^3.716.0", + "@aws-sdk/client-cloudwatch-logs": "^3.716.0", + "@aws-sdk/client-codebuild": "^3.716.0", + "@aws-sdk/client-codedeploy": "^3.716.0", + "@aws-sdk/client-dynamodb": "^3.716.0", + "@aws-sdk/client-ec2": "^3.716.0", + "@aws-sdk/client-ecr": "^3.718.0", + "@aws-sdk/client-ecs": "^3.716.0", + "@aws-sdk/client-efs": "^3.716.0", + "@aws-sdk/client-eks": "^3.718.0", "@aws-sdk/client-elastic-load-balancing-v2": "^3.716.0", + "@aws-sdk/client-elasticache": "^3.716.0", + "@aws-sdk/client-iam": "^3.716.0", "@aws-sdk/client-lambda": "^3.716.0", + "@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-ssm": "^3.716.0", "@aws-sdk/client-sts": "^3.716.0", "@aws-sdk/client-wafv2": "^3.716.0", "@smithy/smithy-client": "^3.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aea00c9..c56390f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@aws-sdk/client-apigatewayv2': specifier: ^3.716.0 version: 3.716.0 + '@aws-sdk/client-application-auto-scaling': + specifier: ^3.716.0 + version: 3.716.0 '@aws-sdk/client-auto-scaling': specifier: ^3.716.0 version: 3.716.0 @@ -20,18 +23,60 @@ importers: '@aws-sdk/client-cloudfront': specifier: ^3.716.0 version: 3.716.0 + '@aws-sdk/client-cloudwatch': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-cloudwatch-logs': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-codebuild': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-codedeploy': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-dynamodb': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-ec2': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-ecr': + specifier: ^3.718.0 + version: 3.718.0 + '@aws-sdk/client-ecs': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-efs': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-eks': + specifier: ^3.718.0 + version: 3.718.0 '@aws-sdk/client-elastic-load-balancing-v2': specifier: ^3.716.0 version: 3.716.0 + '@aws-sdk/client-elasticache': + specifier: ^3.716.0 + version: 3.716.0 + '@aws-sdk/client-iam': + specifier: ^3.716.0 + version: 3.716.0 '@aws-sdk/client-lambda': specifier: ^3.716.0 version: 3.716.0 + '@aws-sdk/client-rds': + specifier: ^3.716.0 + version: 3.716.0 '@aws-sdk/client-s3': specifier: ^3.717.0 version: 3.717.0 '@aws-sdk/client-s3-control': specifier: ^3.716.0 version: 3.716.0 + '@aws-sdk/client-ssm': + specifier: ^3.716.0 + version: 3.716.0 '@aws-sdk/client-sts': specifier: ^3.716.0 version: 3.716.0 @@ -93,6 +138,10 @@ packages: resolution: {integrity: sha512-dv/EgqOh7iAmazzMwcafa8Vs+5lF+MESloBT4RD1TqmHBcxiClxTU6hm9xr+juwOrEApPABEne1L/TSOZwbPLA==} engines: {node: '>=16.0.0'} + '@aws-sdk/client-application-auto-scaling@3.716.0': + resolution: {integrity: sha512-TRPYJPPIvLEkV/fKHZGGGx0FM8VJX5rIymxiyx0pF7jGK//c/RjOINyxyPCUHYoSG2gL/jMr9h3XE3NYV1GU9A==} + engines: {node: '>=16.0.0'} + '@aws-sdk/client-auto-scaling@3.716.0': resolution: {integrity: sha512-L59pJKPfhAdWZoV4Qks8UjeFw+vMVq/QqDMEhszAgvcL8zMpAiLhB28qey20gxOQhD0ZiBDrrSGzJWc2elf3Fw==} engines: {node: '>=16.0.0'} @@ -105,14 +154,66 @@ packages: resolution: {integrity: sha512-r9m+gxgjh/Bt+W3z+F8iR9W0TwBCJQIHt9gjyfIzWa2tLFGzrAajFfs5RUrWpFyl+e8Q6wukAXXm9QhQDwTOsQ==} engines: {node: '>=16.0.0'} + '@aws-sdk/client-cloudwatch-logs@3.716.0': + resolution: {integrity: sha512-jf0Ok+qO8w+X1zCjsSkgjPxG3g9aYyibUt1zydB+GlvG1zSX/vKJo+Ga6DQDYoIut8QragKkiSTV5GsjOhzyXA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-cloudwatch@3.716.0': + resolution: {integrity: sha512-wMoEgiIHdIyUzoxiPfafLdV7EUMnmplfkO2S/i334N1mtby/SxKSBenINiP7hdZVaY3dd/7Cv1jvxDTUxDNuqg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-codebuild@3.716.0': + resolution: {integrity: sha512-D/dRPFoPkIBYojPNk+oJ67lxxAWhXsnMd/NNgS1S7r+mnJ4WyHhQkQ0vLwI2KWMWkiJtwjZw80tjq4HI3X35WQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-codedeploy@3.716.0': + resolution: {integrity: sha512-heh1dUBMlXq4QwCxMQluGtzNUdrC8mZX6OTtVQahO6ZKxM1Tg43WY36Y7IvNvgaRnhDT6eTbuGqF5xJKjGWOIQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-dynamodb@3.716.0': + resolution: {integrity: sha512-/brlrkp5ShSgxSmzj1b7S6ds7iYpqnTlwxdX0ld+bIRrFJH2PS91CAuT1rZiqLG9rarWfk3Of5G0BrclroVnhA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-ec2@3.716.0': + resolution: {integrity: sha512-AKAg47EasPKyFPWcUdf3EsduWIaifuKwyoXZpidhNzdl+LigWwkCfkm8LmZ87+JBuekuMhKc3QIGWLv7wsiP6g==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-ecr@3.718.0': + resolution: {integrity: sha512-bF9aqlAWimltuq7rZPMGP2qCLuy3i1EFCcGnsDfkd0U3SdLVgMc8d2p5qhpvYyAVHf8RvfkUn2Hedflgz9NRnw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-ecs@3.716.0': + resolution: {integrity: sha512-A3GtamWIwwKw8SbdiUWX9yukfXffsOyEYDLc6sAV3PLZ6zCV5ylTG/SOCAu3dfzo6pq55ivghmx4NTBpRGsJCA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-efs@3.716.0': + resolution: {integrity: sha512-I6/GOysU68gwaH7KQTD/8AK6q1iPU73nEAufVR3BNsbRat4kZtYIJjrf4pjVbZQJ3JhT7QyZ93KRqrBblt5A/w==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-eks@3.718.0': + resolution: {integrity: sha512-P67oNE8NNtlz1lgBHhiM8pNFq90Ba758wxQoceL8ghBGcYEcBBL85iZx/Vkkc8XLPlxwcfpxSZDsyuSUpgEqkQ==} + engines: {node: '>=16.0.0'} + '@aws-sdk/client-elastic-load-balancing-v2@3.716.0': resolution: {integrity: sha512-bNj4trUbW17K+/UJEqxoqIDkTSjF3ihvgSon6cKH7hCEiVnYFWop4caJwll2DzyylUIF9AchqOyMEkb4ZBQdQw==} engines: {node: '>=16.0.0'} + '@aws-sdk/client-elasticache@3.716.0': + resolution: {integrity: sha512-840t8JCNN2ATqX8/WzWVo75nj346s56fkjiLI4sz1mtaDF2XIyLvjMe19uGFFJVZGF97ak9p1YExnvicQNYN1g==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-iam@3.716.0': + resolution: {integrity: sha512-6Gpj6pn7QLCIRSoQGskgDjlOPouAao2T3yGep2wgqUa8Zp/e3FP3Q5qG0zznPfWsTqWwBJ4d1HSC31+SOTjZKQ==} + engines: {node: '>=16.0.0'} + '@aws-sdk/client-lambda@3.716.0': resolution: {integrity: sha512-pWcnLQFbG4/xmzq7614YBz6Kbx3Skm3yuQOhac9y163hSmey1bfFdr1bZ+FcxquHbRb5KNvhU1RGmwmNVxsOfg==} engines: {node: '>=16.0.0'} + '@aws-sdk/client-rds@3.716.0': + resolution: {integrity: sha512-LixHgm8Z816m18yUG/GBmOFfG8/rQdqpBEznJ5qy6RQqLfy26ve4EMV70HJrG/AwxZinYLyaLLQFL9+YGY5QTQ==} + engines: {node: '>=16.0.0'} + '@aws-sdk/client-s3-control@3.716.0': resolution: {integrity: sha512-xArSyh24V6EjizTOwcZb60fDTjJaaQOOLckbOwDuQ6MJIc31lq0NOoePyB2VmmdHDwo5Dr8rljTLIkwWZ08QPA==} engines: {node: '>=16.0.0'} @@ -121,6 +222,10 @@ packages: resolution: {integrity: sha512-jzaH8IskAXVnqlZ3/H/ROwrB2HCnq/atlN7Hi7FIfjWvMPf5nfcJKfzJ1MXFX0EQR5qO6X4TbK7rgi7Bjw9NjQ==} 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'} + '@aws-sdk/client-sso-oidc@3.716.0': resolution: {integrity: sha512-lA4IB9FzR2KjH7EVCo+mHGFKqdViVyeBQEIX9oVratL/l7P0bMS1fMwgfHOc3ACazqNxBxDES7x08ZCp32y6Lw==} engines: {node: '>=16.0.0'} @@ -175,10 +280,18 @@ packages: peerDependencies: '@aws-sdk/client-sts': ^3.716.0 + '@aws-sdk/endpoint-cache@3.693.0': + resolution: {integrity: sha512-/zK0ZZncBf5FbTfo8rJMcQIXXk4Ibhe5zEMiwFNivVPR2uNC0+oqfwXz7vjxwY0t6BPE3Bs4h9uFEz4xuGCY6w==} + engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-bucket-endpoint@3.714.0': resolution: {integrity: sha512-I/xSOskiseJJ8i183Z522BgqbgYzLKP7jGcg2Qeib/IWoG2IP+9DH8pwqagKaPAycyswtnoKBJiiFXY43n0CkA==} engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-endpoint-discovery@3.714.0': + resolution: {integrity: sha512-WttOa+M6/aPCK0OHPlWPBaQDTVhfKsWYnmDNvS2d0qvoJEjZuGRyf5DxcA2gWt3MMekxwq9IxOpdA5R9T70HiA==} + engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-expect-continue@3.714.0': resolution: {integrity: sha512-rlzsXdG8Lzo4Qpl35ZnpOBAWlzvDHpP9++0AXoUwAJA0QmMm7auIRmgxJuNj91VwT9h15ZU6xjU4S7fJl4W0+w==} engines: {node: '>=16.0.0'} @@ -203,6 +316,14 @@ packages: resolution: {integrity: sha512-AVU5ixnh93nqtsfgNc284oXsXaadyHGPHpql/jwgaaqQfEXjS/1/j3j9E/vpacfTTz2Vzo7hAOjnvrOXSEVDaA==} engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-sdk-ec2@3.716.0': + resolution: {integrity: sha512-7d2uBQwl9maxINe0cWyi6LY76jqUi0saEvwPH6RbKTqW3OeYKsPNzx/48oWK6byBnLZAxeplN38aWPCcCW207g==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-sdk-rds@3.716.0': + resolution: {integrity: sha512-88Dg1NRWAYHa6kdl9w4Aw8OIhdW9K9xFBjG3A6SjPgpNGVdCWfwXhDmnh1cyG0IsjXx6u8nfeL8e0ZGmjss50Q==} + engines: {node: '>=16.0.0'} + '@aws-sdk/middleware-sdk-s3-control@3.714.0': resolution: {integrity: sha512-OvPVrg0gPEi1CZObau/VRULyBZmU3BKcEVzJXZ5LlG/ApO4hmisnigfcnCh7HEq5OWl2NfQoSyc6hymOICFdyw==} engines: {node: '>=16.0.0'} @@ -245,6 +366,10 @@ packages: resolution: {integrity: sha512-Xv+Z2lhe7w7ZZRsgBwBMZgGTVmS+dkkj2S13uNHAx9lhB5ovM8PhK5G/j28xYf6vIibeuHkRAbb7/ozdZIGR+A==} engines: {node: '>=16.0.0'} + '@aws-sdk/util-format-url@3.714.0': + resolution: {integrity: sha512-PA/ES6BeKmYzFOsZ3az/8MqSLf6uzXAS7GsYONZMF6YASn4ewd/AspuvQMp6+x9VreAPCq7PecF+XL9KXejtPg==} + engines: {node: '>=16.0.0'} + '@aws-sdk/util-locate-window@3.693.0': resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} engines: {node: '>=16.0.0'} @@ -433,6 +558,10 @@ packages: resolution: {integrity: sha512-G8G/sDDhXA7o0bOvkc7bgai6POuSld/+XhNnWAbpQTpLv2OZPvyqQ58tLPPlz0bSNsXktldDDREIv1LczFeNEw==} engines: {node: '>=16.0.0'} + '@smithy/core@2.5.6': + resolution: {integrity: sha512-w494xO+CPwG/5B/N2l0obHv2Fi9U4DAY+sTi1GWT3BVvGpZetJjJXAynIO9IHp4zS1PinGhXtRSZydUXbJO4ag==} + engines: {node: '>=16.0.0'} + '@smithy/credential-provider-imds@3.2.8': resolution: {integrity: sha512-ZCY2yD0BY+K9iMXkkbnjo+08T2h8/34oHd0Jmh6BZUSZwaaGlGCyBT/3wnS7u7Xl33/EEfN4B6nQr3Gx5bYxgw==} engines: {node: '>=16.0.0'} @@ -488,6 +617,10 @@ packages: resolution: {integrity: sha512-iqnbVTl9iSQCmrEY9ZkXgUitgzkY3+KApmb8mMpIAyxLDG2yAQXt8QnXExNy2Z/6uabr7tjuaKySXfDurwcLiw==} engines: {node: '>=16.0.0'} + '@smithy/middleware-compression@3.1.6': + resolution: {integrity: sha512-LWb4SA8zZ0FEHKzlQV5wAmnkfimPYIcc7OXxTihA954pDpXasIu3WPYBsfdqLsldQUW40S6mhjHQzUYLl8P5Jw==} + engines: {node: '>=16.0.0'} + '@smithy/middleware-content-length@3.0.13': resolution: {integrity: sha512-zfMhzojhFpIX3P5ug7jxTjfUcIPcGjcQYzB9t+rv0g1TX7B0QdwONW+ATouaLoD7h7LOw/ZlXfkq4xJ/g2TrIw==} engines: {node: '>=16.0.0'} @@ -516,6 +649,10 @@ packages: resolution: {integrity: sha512-t4ng1DAd527vlxvOfKFYEe6/QFBcsj7WpNlWTyjorwXXcKw3XlltBGbyHfSJ24QT84nF+agDha9tNYpzmSRZPA==} engines: {node: '>=16.0.0'} + '@smithy/node-http-handler@3.3.3': + resolution: {integrity: sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==} + engines: {node: '>=16.0.0'} + '@smithy/property-provider@3.1.11': resolution: {integrity: sha512-I/+TMc4XTQ3QAjXfOcUWbSS073oOEAxgx4aZy8jHaf8JQnRkq2SZWw8+PfDtBvLUjcGMdxl+YwtzWe6i5uhL/A==} engines: {node: '>=16.0.0'} @@ -606,6 +743,10 @@ packages: resolution: {integrity: sha512-sInAqdiVeisUGYAv/FrXpmJ0b4WTFmciTRqzhb7wVuem9BHvhIG7tpiYHLDWrl2stOokNZpTTGqz3mzB2qFwXg==} engines: {node: '>=16.0.0'} + '@smithy/util-stream@3.3.3': + resolution: {integrity: sha512-bOm0YMMxRjbI3X6QkWwADPFkh2AH2xBMQIB1IQgCsCRqXXpSJatgjUR3oxHthpYwFkw3WPkOt8VgMpJxC0rFqg==} + engines: {node: '>=16.0.0'} + '@smithy/util-uri-escape@3.0.0': resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} engines: {node: '>=16.0.0'} @@ -765,6 +906,9 @@ packages: resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true + fflate@0.8.1: + resolution: {integrity: sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==} + finalhandler@1.3.1: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} @@ -839,6 +983,9 @@ packages: engines: {node: '>=4'} hasBin: true + mnemonist@0.38.3: + resolution: {integrity: sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -853,6 +1000,9 @@ packages: resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} engines: {node: '>= 0.4'} + obliterator@1.6.1: + resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -1055,6 +1205,52 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-application-auto-scaling@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-auto-scaling@3.716.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -1199,6 +1395,491 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-cloudwatch-logs@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.5 + '@smithy/eventstream-serde-browser': 3.0.14 + '@smithy/eventstream-serde-config-resolver': 3.0.11 + '@smithy/eventstream-serde-node': 3.0.13 + '@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.2 + '@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-cloudwatch@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.5 + '@smithy/fetch-http-handler': 4.1.2 + '@smithy/hash-node': 3.0.11 + '@smithy/invalid-dependency': 3.0.11 + '@smithy/middleware-compression': 3.1.6 + '@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.2 + '@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 + '@smithy/util-waiter': 3.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-codebuild@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-codedeploy@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 + '@smithy/util-waiter': 3.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-dynamodb@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-endpoint-discovery': 3.714.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 + '@smithy/util-waiter': 3.2.0 + '@types/uuid': 9.0.8 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-ec2@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-sdk-ec2': 3.716.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 + '@smithy/util-waiter': 3.2.0 + '@types/uuid': 9.0.8 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-ecr@3.718.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 + '@smithy/util-waiter': 3.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-ecs@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 + '@smithy/util-waiter': 3.2.0 + '@types/uuid': 9.0.8 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-efs@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-eks@3.718.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 + '@smithy/util-waiter': 3.2.0 + '@types/uuid': 9.0.8 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-elastic-load-balancing-v2@3.716.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -1246,6 +1927,100 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-elasticache@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 + '@smithy/util-waiter': 3.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-iam@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 + '@smithy/util-waiter': 3.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-lambda@3.716.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -1297,6 +2072,54 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-rds@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-sdk-rds': 3.716.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 + '@smithy/util-waiter': 3.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-s3-control@3.716.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -1414,6 +2237,55 @@ snapshots: transitivePeerDependencies: - aws-crt + '@aws-sdk/client-ssm@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 + '@smithy/util-waiter': 3.2.0 + '@types/uuid': 9.0.8 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-sso-oidc@3.716.0(@aws-sdk/client-sts@3.716.0)': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -1698,6 +2570,11 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@aws-sdk/endpoint-cache@3.693.0': + dependencies: + mnemonist: 0.38.3 + tslib: 2.8.1 + '@aws-sdk/middleware-bucket-endpoint@3.714.0': dependencies: '@aws-sdk/types': 3.714.0 @@ -1708,6 +2585,15 @@ snapshots: '@smithy/util-config-provider': 3.0.0 tslib: 2.8.1 + '@aws-sdk/middleware-endpoint-discovery@3.714.0': + dependencies: + '@aws-sdk/endpoint-cache': 3.693.0 + '@aws-sdk/types': 3.714.0 + '@smithy/node-config-provider': 3.1.12 + '@smithy/protocol-http': 4.1.8 + '@smithy/types': 3.7.2 + tslib: 2.8.1 + '@aws-sdk/middleware-expect-continue@3.714.0': dependencies: '@aws-sdk/types': 3.714.0 @@ -1757,6 +2643,27 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@aws-sdk/middleware-sdk-ec2@3.716.0': + dependencies: + '@aws-sdk/types': 3.714.0 + '@aws-sdk/util-format-url': 3.714.0 + '@smithy/middleware-endpoint': 3.2.6 + '@smithy/protocol-http': 4.1.8 + '@smithy/signature-v4': 4.2.4 + '@smithy/smithy-client': 3.5.1 + '@smithy/types': 3.7.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-rds@3.716.0': + dependencies: + '@aws-sdk/types': 3.714.0 + '@aws-sdk/util-format-url': 3.714.0 + '@smithy/middleware-endpoint': 3.2.6 + '@smithy/protocol-http': 4.1.8 + '@smithy/signature-v4': 4.2.4 + '@smithy/types': 3.7.2 + tslib: 2.8.1 + '@aws-sdk/middleware-sdk-s3-control@3.714.0': dependencies: '@aws-sdk/middleware-bucket-endpoint': 3.714.0 @@ -1844,6 +2751,13 @@ snapshots: '@smithy/util-endpoints': 2.1.7 tslib: 2.8.1 + '@aws-sdk/util-format-url@3.714.0': + dependencies: + '@aws-sdk/types': 3.714.0 + '@smithy/querystring-builder': 3.0.11 + '@smithy/types': 3.7.2 + tslib: 2.8.1 + '@aws-sdk/util-locate-window@3.693.0': dependencies: tslib: 2.8.1 @@ -1976,6 +2890,17 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + '@smithy/core@2.5.6': + dependencies: + '@smithy/middleware-serde': 3.0.11 + '@smithy/protocol-http': 4.1.8 + '@smithy/types': 3.7.2 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-middleware': 3.0.11 + '@smithy/util-stream': 3.3.3 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + '@smithy/credential-provider-imds@3.2.8': dependencies: '@smithy/node-config-provider': 3.1.12 @@ -2068,6 +2993,19 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/middleware-compression@3.1.6': + dependencies: + '@smithy/core': 2.5.6 + '@smithy/is-array-buffer': 3.0.0 + '@smithy/node-config-provider': 3.1.12 + '@smithy/protocol-http': 4.1.8 + '@smithy/types': 3.7.2 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.11 + '@smithy/util-utf8': 3.0.0 + fflate: 0.8.1 + tslib: 2.8.1 + '@smithy/middleware-content-length@3.0.13': dependencies: '@smithy/protocol-http': 4.1.8 @@ -2122,6 +3060,14 @@ snapshots: '@smithy/types': 3.7.2 tslib: 2.8.1 + '@smithy/node-http-handler@3.3.3': + dependencies: + '@smithy/abort-controller': 3.1.9 + '@smithy/protocol-http': 4.1.8 + '@smithy/querystring-builder': 3.0.11 + '@smithy/types': 3.7.2 + tslib: 2.8.1 + '@smithy/property-provider@3.1.11': dependencies: '@smithy/types': 3.7.2 @@ -2261,6 +3207,17 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + '@smithy/util-stream@3.3.3': + dependencies: + '@smithy/fetch-http-handler': 4.1.2 + '@smithy/node-http-handler': 3.3.3 + '@smithy/types': 3.7.2 + '@smithy/util-base64': 3.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + '@smithy/util-uri-escape@3.0.0': dependencies: tslib: 2.8.1 @@ -2481,6 +3438,8 @@ snapshots: dependencies: strnum: 1.0.5 + fflate@0.8.1: {} + finalhandler@1.3.1: dependencies: debug: 2.6.9 @@ -2552,6 +3511,10 @@ snapshots: mime@1.6.0: {} + mnemonist@0.38.3: + dependencies: + obliterator: 1.6.1 + ms@2.0.0: {} ms@2.1.3: {} @@ -2560,6 +3523,8 @@ snapshots: object-inspect@1.13.3: {} + obliterator@1.6.1: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 diff --git a/src/bpsets/cloudwatch/CWLogGroupRetentionPeriodCheck.ts b/src/bpsets/cloudwatch/CWLogGroupRetentionPeriodCheck.ts new file mode 100644 index 0000000..eacac82 --- /dev/null +++ b/src/bpsets/cloudwatch/CWLogGroupRetentionPeriodCheck.ts @@ -0,0 +1,60 @@ +import { + CloudWatchLogsClient, + DescribeLogGroupsCommand, + PutRetentionPolicyCommand +} from '@aws-sdk/client-cloudwatch-logs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class CWLogGroupRetentionPeriodCheck implements BPSet { + private readonly client = new CloudWatchLogsClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getLogGroups = async () => { + const response = await this.memoClient.send(new DescribeLogGroupsCommand({})) + return response.logGroups || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const logGroups = await this.getLogGroups() + + for (const logGroup of logGroups) { + if (logGroup.retentionInDays) { + compliantResources.push(logGroup.logGroupArn!) + } else { + nonCompliantResources.push(logGroup.logGroupArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'retention-period-days' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const retentionPeriod = requiredParametersForFix.find( + param => param.name === 'retention-period-days' + )?.value + + if (!retentionPeriod) { + throw new Error("Required parameter 'retention-period-days' is missing.") + } + + for (const logGroupArn of nonCompliantResources) { + const logGroupName = logGroupArn.split(':').pop()! + await this.client.send( + new PutRetentionPolicyCommand({ + logGroupName, + retentionInDays: parseInt(retentionPeriod, 10) + }) + ) + } + } +} diff --git a/src/bpsets/cloudwatch/CloudWatchAlarmSettingsCheck.ts b/src/bpsets/cloudwatch/CloudWatchAlarmSettingsCheck.ts new file mode 100644 index 0000000..b2efaf2 --- /dev/null +++ b/src/bpsets/cloudwatch/CloudWatchAlarmSettingsCheck.ts @@ -0,0 +1,85 @@ +import { + CloudWatchClient, + DescribeAlarmsCommand, + PutMetricAlarmCommand +} from '@aws-sdk/client-cloudwatch' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class CloudWatchAlarmSettingsCheck implements BPSet { + private readonly client = new CloudWatchClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getAlarms = async () => { + const response = await this.memoClient.send(new DescribeAlarmsCommand({})) + return response.MetricAlarms || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const parameters = { + MetricName: '', // Required + Threshold: null, + EvaluationPeriods: null, + Period: null, + ComparisonOperator: null, + Statistic: null + } + + const alarms = await this.getAlarms() + + for (const alarm of alarms) { + for (const parameter of Object.keys(parameters).filter(key => (parameters as any)[key] !== null)) { + if (alarm.MetricName !== parameters.MetricName) { + continue + } + + if (alarm[parameter as keyof typeof alarm] !== parameters[parameter as keyof typeof parameters]) { + nonCompliantResources.push(alarm.AlarmArn!) + break + } + } + + compliantResources.push(alarm.AlarmArn!) + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [ + { name: 'metric-name' }, + { name: 'threshold' }, + { name: 'evaluation-periods' }, + { name: 'period' }, + { name: 'comparison-operator' }, + { name: 'statistic' } + ] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const requiredSettings = Object.fromEntries( + requiredParametersForFix.map(param => [param.name, param.value]) + ) + + for (const alarmArn of nonCompliantResources) { + const alarmName = alarmArn.split(':').pop()! + + await this.client.send( + new PutMetricAlarmCommand({ + AlarmName: alarmName, + MetricName: requiredSettings['metric-name'], + Threshold: parseFloat(requiredSettings['threshold']), + EvaluationPeriods: parseInt(requiredSettings['evaluation-periods'], 10), + Period: parseInt(requiredSettings['period'], 10), + ComparisonOperator: requiredSettings['comparison-operator'] as any, + Statistic: requiredSettings['statistic'] as any + }) + ) + } + } +} diff --git a/src/bpsets/codeseries/CodeBuildProjectEnvironmentPrivilegedCheck.ts b/src/bpsets/codeseries/CodeBuildProjectEnvironmentPrivilegedCheck.ts new file mode 100644 index 0000000..87031b4 --- /dev/null +++ b/src/bpsets/codeseries/CodeBuildProjectEnvironmentPrivilegedCheck.ts @@ -0,0 +1,66 @@ +import { + CodeBuildClient, + ListProjectsCommand, + BatchGetProjectsCommand, + UpdateProjectCommand +} from '@aws-sdk/client-codebuild' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class CodeBuildProjectEnvironmentPrivilegedCheck implements BPSet { + private readonly client = new CodeBuildClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getProjects = async () => { + const projectNames = await this.memoClient.send(new ListProjectsCommand({})) + if (!projectNames.projects?.length) { + return [] + } + const response = await this.memoClient.send( + new BatchGetProjectsCommand({ names: projectNames.projects }) + ) + return response.projects || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const projects = await this.getProjects() + + for (const project of projects) { + if (!project.environment?.privilegedMode) { + compliantResources.push(project.arn!) + } else { + nonCompliantResources.push(project.arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const projectName = arn.split(':').pop()! + const projects = await this.getProjects() + const projectToFix = projects.find(project => project.arn === arn) + + if (!projectToFix) { + continue + } + + await this.client.send( + new UpdateProjectCommand({ + name: projectName, + environment: { + ...projectToFix.environment as any, + privilegedMode: false + } + }) + ) + } + } +} diff --git a/src/bpsets/codeseries/CodeBuildProjectLoggingEnabled.ts b/src/bpsets/codeseries/CodeBuildProjectLoggingEnabled.ts new file mode 100644 index 0000000..3a00dcf --- /dev/null +++ b/src/bpsets/codeseries/CodeBuildProjectLoggingEnabled.ts @@ -0,0 +1,74 @@ +import { + CodeBuildClient, + ListProjectsCommand, + BatchGetProjectsCommand, + UpdateProjectCommand +} from '@aws-sdk/client-codebuild' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class CodeBuildProjectLoggingEnabled implements BPSet { + private readonly client = new CodeBuildClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getProjects = async () => { + const projectNames = await this.memoClient.send(new ListProjectsCommand({})) + if (!projectNames.projects?.length) { + return [] + } + const response = await this.memoClient.send( + new BatchGetProjectsCommand({ names: projectNames.projects }) + ) + return response.projects || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const projects = await this.getProjects() + + for (const project of projects) { + const logsConfig = project.logsConfig + if ( + logsConfig?.cloudWatchLogs?.status === 'ENABLED' || + logsConfig?.s3Logs?.status === 'ENABLED' + ) { + compliantResources.push(project.arn!) + } else { + nonCompliantResources.push(project.arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const projectName = arn.split(':').pop()! + const projects = await this.getProjects() + const projectToFix = projects.find(project => project.arn === arn) + + if (!projectToFix) { + continue + } + + await this.client.send( + new UpdateProjectCommand({ + name: projectName, + logsConfig: { + ...projectToFix.logsConfig, + cloudWatchLogs: { + status: 'ENABLED', + groupName: 'default-cloudwatch-group', + streamName: 'default-stream' + } + } + }) + ) + } + } +} diff --git a/src/bpsets/codeseries/CodeDeployAutoRollbackMonitorEnabled.ts b/src/bpsets/codeseries/CodeDeployAutoRollbackMonitorEnabled.ts new file mode 100644 index 0000000..f9e9ba2 --- /dev/null +++ b/src/bpsets/codeseries/CodeDeployAutoRollbackMonitorEnabled.ts @@ -0,0 +1,88 @@ +import { + CodeDeployClient, + ListApplicationsCommand, + ListDeploymentGroupsCommand, + BatchGetDeploymentGroupsCommand, + UpdateDeploymentGroupCommand +} from '@aws-sdk/client-codedeploy' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class CodeDeployAutoRollbackMonitorEnabled implements BPSet { + private readonly client = new CodeDeployClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDeploymentGroups = async () => { + const applications = await this.memoClient.send(new ListApplicationsCommand({})) + const deploymentGroupsInfo = [] + + for (const application of applications.applications || []) { + const deploymentGroups = await this.memoClient.send( + new ListDeploymentGroupsCommand({ applicationName: application }) + ) + if (!deploymentGroups.deploymentGroups?.length) { + continue + } + const batchResponse = await this.memoClient.send( + new BatchGetDeploymentGroupsCommand({ + applicationName: application, + deploymentGroupNames: deploymentGroups.deploymentGroups + }) + ) + deploymentGroupsInfo.push(...(batchResponse.deploymentGroupsInfo || [])) + } + + return deploymentGroupsInfo + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const deploymentGroups = await this.getDeploymentGroups() + + for (const deploymentGroup of deploymentGroups) { + if ( + deploymentGroup.alarmConfiguration?.enabled && + deploymentGroup.autoRollbackConfiguration?.enabled + ) { + compliantResources.push(deploymentGroup.deploymentGroupId!) + } else { + nonCompliantResources.push(deploymentGroup.deploymentGroupId!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const groupId of nonCompliantResources) { + const deploymentGroups = await this.getDeploymentGroups() + const deploymentGroupToFix = deploymentGroups.find( + group => group.deploymentGroupId === groupId + ) + + if (!deploymentGroupToFix) { + continue + } + + await this.client.send( + new UpdateDeploymentGroupCommand({ + applicationName: deploymentGroupToFix.applicationName!, + currentDeploymentGroupName: deploymentGroupToFix.deploymentGroupName!, + alarmConfiguration: { + ...deploymentGroupToFix.alarmConfiguration, + enabled: true + }, + autoRollbackConfiguration: { + ...deploymentGroupToFix.autoRollbackConfiguration, + enabled: true + } + }) + ) + } + } +} diff --git a/src/bpsets/dynamodb/DynamoDBAutoscalingEnabled.ts b/src/bpsets/dynamodb/DynamoDBAutoscalingEnabled.ts new file mode 100644 index 0000000..f202ddd --- /dev/null +++ b/src/bpsets/dynamodb/DynamoDBAutoscalingEnabled.ts @@ -0,0 +1,133 @@ +import { + DynamoDBClient, + ListTablesCommand, + DescribeTableCommand +} from '@aws-sdk/client-dynamodb' +import { + ApplicationAutoScalingClient, + RegisterScalableTargetCommand, + PutScalingPolicyCommand, + DescribeScalingPoliciesCommand +} from '@aws-sdk/client-application-auto-scaling' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class DynamoDBAutoscalingEnabled implements BPSet { + private readonly client = new DynamoDBClient({}) + private readonly autoScalingClient = new ApplicationAutoScalingClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTables = async () => { + const tableNames = await this.memoClient.send(new ListTablesCommand({})) + const tables = [] + for (const tableName of tableNames.TableNames || []) { + const tableDetails = await this.memoClient.send( + new DescribeTableCommand({ TableName: tableName }) + ) + tables.push(tableDetails.Table!) + } + return tables + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const tables = await this.getTables() + + for (const table of tables) { + if (table.BillingModeSummary?.BillingMode === 'PAY_PER_REQUEST') { + compliantResources.push(table.TableArn!) + continue + } + + const scalingPolicies = await this.autoScalingClient.send( + new DescribeScalingPoliciesCommand({ + ServiceNamespace: 'dynamodb', + ResourceId: `table/${table.TableName}` + }) + ) + const scalingPolicyDimensions = scalingPolicies.ScalingPolicies?.map( + policy => policy.ScalableDimension + ) + + if ( + scalingPolicyDimensions?.includes('dynamodb:table:ReadCapacityUnits') && + scalingPolicyDimensions?.includes('dynamodb:table:WriteCapacityUnits') + ) { + compliantResources.push(table.TableArn!) + } else { + nonCompliantResources.push(table.TableArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const tableName = arn.split('/').pop()! + + // Register scalable targets for read and write capacity + await this.autoScalingClient.send( + new RegisterScalableTargetCommand({ + ServiceNamespace: 'dynamodb', + ResourceId: `table/${tableName}`, + ScalableDimension: 'dynamodb:table:ReadCapacityUnits', + MinCapacity: 1, + MaxCapacity: 100 + }) + ) + + await this.autoScalingClient.send( + new RegisterScalableTargetCommand({ + ServiceNamespace: 'dynamodb', + ResourceId: `table/${tableName}`, + ScalableDimension: 'dynamodb:table:WriteCapacityUnits', + MinCapacity: 1, + MaxCapacity: 100 + }) + ) + + // Put scaling policies for read and write capacity + await this.autoScalingClient.send( + new PutScalingPolicyCommand({ + ServiceNamespace: 'dynamodb', + ResourceId: `table/${tableName}`, + ScalableDimension: 'dynamodb:table:ReadCapacityUnits', + PolicyName: `${tableName}-ReadPolicy`, + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + TargetValue: 70.0, + ScaleInCooldown: 60, + ScaleOutCooldown: 60, + PredefinedMetricSpecification: { + PredefinedMetricType: 'DynamoDBReadCapacityUtilization' + } + } + }) + ) + + await this.autoScalingClient.send( + new PutScalingPolicyCommand({ + ServiceNamespace: 'dynamodb', + ResourceId: `table/${tableName}`, + ScalableDimension: 'dynamodb:table:WriteCapacityUnits', + PolicyName: `${tableName}-WritePolicy`, + PolicyType: 'TargetTrackingScaling', + TargetTrackingScalingPolicyConfiguration: { + TargetValue: 70.0, + ScaleInCooldown: 60, + ScaleOutCooldown: 60, + PredefinedMetricSpecification: { + PredefinedMetricType: 'DynamoDBWriteCapacityUtilization' + } + } + }) + ) + } + } +} diff --git a/src/bpsets/dynamodb/DynamoDBLastBackupRecoveryPointCreated.ts b/src/bpsets/dynamodb/DynamoDBLastBackupRecoveryPointCreated.ts new file mode 100644 index 0000000..7d5dc55 --- /dev/null +++ b/src/bpsets/dynamodb/DynamoDBLastBackupRecoveryPointCreated.ts @@ -0,0 +1,78 @@ +import { + DynamoDBClient, + ListTablesCommand, + DescribeTableCommand +} from '@aws-sdk/client-dynamodb' +import { + BackupClient, + ListRecoveryPointsByResourceCommand, + StartBackupJobCommand +} from '@aws-sdk/client-backup' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class DynamoDBLastBackupRecoveryPointCreated implements BPSet { + private readonly client = new DynamoDBClient({}) + private readonly backupClient = new BackupClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTables = async () => { + const tableNames = await this.memoClient.send(new ListTablesCommand({})) + const tables = [] + for (const tableName of tableNames.TableNames || []) { + const tableDetails = await this.memoClient.send( + new DescribeTableCommand({ TableName: tableName }) + ) + tables.push(tableDetails.Table!) + } + return tables + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const tables = await this.getTables() + + for (const table of tables) { + const recoveryPointsResponse = await this.backupClient.send( + new ListRecoveryPointsByResourceCommand({ + ResourceArn: table.TableArn + }) + ) + const recoveryPoints = recoveryPointsResponse.RecoveryPoints || [] + + if (recoveryPoints.length === 0) { + nonCompliantResources.push(table.TableArn!) + continue + } + + const latestRecoveryPoint = recoveryPoints + .map(point => new Date(point.CreationDate!)) + .sort((a, b) => b.getTime() - a.getTime())[0] + + if (new Date().getTime() - latestRecoveryPoint.getTime() > 86400000) { + nonCompliantResources.push(table.TableArn!) + } else { + compliantResources.push(table.TableArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + await this.backupClient.send( + new StartBackupJobCommand({ + ResourceArn: arn, + BackupVaultName: 'Default', + IamRoleArn: 'arn:aws:iam::account-id:role/service-role/BackupDefaultServiceRole', + }) + ) + } + } +} diff --git a/src/bpsets/dynamodb/DynamoDBPITREnabled.ts b/src/bpsets/dynamodb/DynamoDBPITREnabled.ts new file mode 100644 index 0000000..332f688 --- /dev/null +++ b/src/bpsets/dynamodb/DynamoDBPITREnabled.ts @@ -0,0 +1,70 @@ +import { + DynamoDBClient, + ListTablesCommand, + DescribeTableCommand, + DescribeContinuousBackupsCommand, + UpdateContinuousBackupsCommand +} from '@aws-sdk/client-dynamodb' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class DynamoDBPITREnabled implements BPSet { + private readonly client = new DynamoDBClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTables = async () => { + const tableNames = await this.memoClient.send(new ListTablesCommand({})) + const tables = [] + for (const tableName of tableNames.TableNames || []) { + const tableDetails = await this.memoClient.send( + new DescribeTableCommand({ TableName: tableName }) + ) + tables.push(tableDetails.Table!) + } + return tables + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const tables = await this.getTables() + + for (const table of tables) { + const backupStatus = await this.memoClient.send( + new DescribeContinuousBackupsCommand({ + TableName: table.TableName! + }) + ) + + if ( + backupStatus.ContinuousBackupsDescription?.PointInTimeRecoveryDescription + ?.PointInTimeRecoveryStatus === 'ENABLED' + ) { + compliantResources.push(table.TableArn!) + } else { + nonCompliantResources.push(table.TableArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const tableName = arn.split('/').pop()! + + await this.client.send( + new UpdateContinuousBackupsCommand({ + TableName: tableName, + PointInTimeRecoverySpecification: { + PointInTimeRecoveryEnabled: true + } + }) + ) + } + } +} diff --git a/src/bpsets/dynamodb/DynamoDBTableDeletionProtectionEnabled.ts b/src/bpsets/dynamodb/DynamoDBTableDeletionProtectionEnabled.ts new file mode 100644 index 0000000..933d14e --- /dev/null +++ b/src/bpsets/dynamodb/DynamoDBTableDeletionProtectionEnabled.ts @@ -0,0 +1,58 @@ +import { + DynamoDBClient, + ListTablesCommand, + DescribeTableCommand, + UpdateTableCommand +} from '@aws-sdk/client-dynamodb' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class DynamoDBTableDeletionProtectionEnabled implements BPSet { + private readonly client = new DynamoDBClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTables = async () => { + const tableNames = await this.memoClient.send(new ListTablesCommand({})) + const tables = [] + for (const tableName of tableNames.TableNames || []) { + const tableDetails = await this.memoClient.send( + new DescribeTableCommand({ TableName: tableName }) + ) + tables.push(tableDetails.Table!) + } + return tables + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const tables = await this.getTables() + + for (const table of tables) { + if (table.DeletionProtectionEnabled) { + compliantResources.push(table.TableArn!) + } else { + nonCompliantResources.push(table.TableArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const tableName = arn.split('/').pop()! + + await this.client.send( + new UpdateTableCommand({ + TableName: tableName, + DeletionProtectionEnabled: true + }) + ) + } + } +} diff --git a/src/bpsets/dynamodb/DynamoDBTableEncryptedKMS.ts b/src/bpsets/dynamodb/DynamoDBTableEncryptedKMS.ts new file mode 100644 index 0000000..916d63e --- /dev/null +++ b/src/bpsets/dynamodb/DynamoDBTableEncryptedKMS.ts @@ -0,0 +1,74 @@ +import { + DynamoDBClient, + ListTablesCommand, + DescribeTableCommand, + UpdateTableCommand +} from '@aws-sdk/client-dynamodb' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class DynamoDBTableEncryptedKMS implements BPSet { + private readonly client = new DynamoDBClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTables = async () => { + const tableNames = await this.memoClient.send(new ListTablesCommand({})) + const tables = [] + for (const tableName of tableNames.TableNames || []) { + const tableDetails = await this.memoClient.send( + new DescribeTableCommand({ TableName: tableName }) + ) + tables.push(tableDetails.Table!) + } + return tables + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const tables = await this.getTables() + + for (const table of tables) { + if ( + table.SSEDescription?.Status === 'ENABLED' && + table.SSEDescription?.SSEType === 'KMS' + ) { + compliantResources.push(table.TableArn!) + } else { + nonCompliantResources.push(table.TableArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: '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) { + const tableName = arn.split('/').pop()! + + await this.client.send( + new UpdateTableCommand({ + TableName: tableName, + SSESpecification: { + Enabled: true, + SSEType: 'KMS', + KMSMasterKeyId: kmsKeyId + } + }) + ) + } + } +} diff --git a/src/bpsets/dynamodb/DynamoDBTableEncryptionEnabled.ts b/src/bpsets/dynamodb/DynamoDBTableEncryptionEnabled.ts new file mode 100644 index 0000000..f705e91 --- /dev/null +++ b/src/bpsets/dynamodb/DynamoDBTableEncryptionEnabled.ts @@ -0,0 +1,60 @@ +import { + DynamoDBClient, + ListTablesCommand, + DescribeTableCommand, + UpdateTableCommand +} from '@aws-sdk/client-dynamodb' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class DynamoDBTableEncryptionEnabled implements BPSet { + private readonly client = new DynamoDBClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTables = async () => { + const tableNames = await this.memoClient.send(new ListTablesCommand({})) + const tables = [] + for (const tableName of tableNames.TableNames || []) { + const tableDetails = await this.memoClient.send( + new DescribeTableCommand({ TableName: tableName }) + ) + tables.push(tableDetails.Table!) + } + return tables + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const tables = await this.getTables() + + for (const table of tables) { + if (table.SSEDescription?.Status === 'ENABLED') { + compliantResources.push(table.TableArn!) + } else { + nonCompliantResources.push(table.TableArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const tableName = arn.split('/').pop()! + + await this.client.send( + new UpdateTableCommand({ + TableName: tableName, + SSESpecification: { + Enabled: true + } + }) + ) + } + } +} diff --git a/src/bpsets/ec2/EC2EbsEncryptionByDefault.ts b/src/bpsets/ec2/EC2EbsEncryptionByDefault.ts new file mode 100644 index 0000000..c9e49a4 --- /dev/null +++ b/src/bpsets/ec2/EC2EbsEncryptionByDefault.ts @@ -0,0 +1,36 @@ +import { + EC2Client, + DescribeVolumesCommand, + EnableEbsEncryptionByDefaultCommand +} from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2EbsEncryptionByDefault implements BPSet { + private readonly client = new EC2Client({}) + private readonly memoClient = Memorizer.memo(this.client) + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const response = await this.memoClient.send(new DescribeVolumesCommand({})) + + for (const volume of response.Volumes || []) { + if (volume.Encrypted) { + compliantResources.push(volume.VolumeId!) + } else { + nonCompliantResources.push(volume.VolumeId!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async () => { + await this.client.send(new EnableEbsEncryptionByDefaultCommand({})) + } +} diff --git a/src/bpsets/ec2/EC2Imdsv2Check.ts b/src/bpsets/ec2/EC2Imdsv2Check.ts new file mode 100644 index 0000000..166d2cf --- /dev/null +++ b/src/bpsets/ec2/EC2Imdsv2Check.ts @@ -0,0 +1,45 @@ +import { + DescribeInstancesCommand, + EC2Client, + ModifyInstanceMetadataOptionsCommand +} from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2Imdsv2Check implements BPSet { + private readonly client = new EC2Client({}) + private readonly memoClient = Memorizer.memo(this.client) + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const response = await this.memoClient.send(new DescribeInstancesCommand({})) + + for (const reservation of response.Reservations || []) { + for (const instance of reservation.Instances || []) { + if (instance.MetadataOptions?.HttpTokens === 'required') { + compliantResources.push(instance.InstanceId!) + } else { + nonCompliantResources.push(instance.InstanceId!) + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const instanceId of nonCompliantResources) { + await this.client.send( + new ModifyInstanceMetadataOptionsCommand({ + InstanceId: instanceId, + HttpTokens: 'required' + }) + ) + } + } +} diff --git a/src/bpsets/ec2/EC2InstanceDetailedMonitoringEnabled.ts b/src/bpsets/ec2/EC2InstanceDetailedMonitoringEnabled.ts new file mode 100644 index 0000000..3c10a11 --- /dev/null +++ b/src/bpsets/ec2/EC2InstanceDetailedMonitoringEnabled.ts @@ -0,0 +1,42 @@ +import { + DescribeInstancesCommand, + EC2Client, + MonitorInstancesCommand +} from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2InstanceDetailedMonitoringEnabled implements BPSet { + private readonly client = new EC2Client({}) + private readonly memoClient = Memorizer.memo(this.client) + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const response = await this.memoClient.send(new DescribeInstancesCommand({})) + + for (const reservation of response.Reservations || []) { + for (const instance of reservation.Instances || []) { + if (instance.Monitoring?.State === 'enabled') { + compliantResources.push(instance.InstanceId!) + } else { + nonCompliantResources.push(instance.InstanceId!) + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + await this.client.send( + new MonitorInstancesCommand({ + InstanceIds: nonCompliantResources + }) + ) + } +} diff --git a/src/bpsets/ec2/EC2InstanceManagedBySystemsManager.ts b/src/bpsets/ec2/EC2InstanceManagedBySystemsManager.ts new file mode 100644 index 0000000..a1c0dfa --- /dev/null +++ b/src/bpsets/ec2/EC2InstanceManagedBySystemsManager.ts @@ -0,0 +1,48 @@ +import { + EC2Client, + DescribeInstancesCommand +} from '@aws-sdk/client-ec2' +import { SSMClient, DescribeInstanceInformationCommand } from '@aws-sdk/client-ssm' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2InstanceManagedBySystemsManager implements BPSet { + private readonly client = new EC2Client({}) + private readonly ssmClient = new SSMClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const response = await this.memoClient.send(new DescribeInstancesCommand({})) + const ssmResponse = await this.ssmClient.send( + new DescribeInstanceInformationCommand({}) + ) + + const managedInstanceIds = ssmResponse.InstanceInformationList?.map( + info => info.InstanceId + ) + + for (const reservation of response.Reservations || []) { + for (const instance of reservation.Instances || []) { + if (managedInstanceIds?.includes(instance.InstanceId!)) { + compliantResources.push(instance.InstanceId!) + } else { + nonCompliantResources.push(instance.InstanceId!) + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async () => { + throw new Error( + 'Fix logic for EC2InstanceManagedBySystemsManager is not directly applicable. Systems Manager Agent setup requires manual intervention.' + ) + } +} diff --git a/src/bpsets/ec2/EC2InstanceProfileAttached,ts b/src/bpsets/ec2/EC2InstanceProfileAttached,ts new file mode 100644 index 0000000..13b534f --- /dev/null +++ b/src/bpsets/ec2/EC2InstanceProfileAttached,ts @@ -0,0 +1,56 @@ +import { + EC2Client, + DescribeInstancesCommand, + AssociateIamInstanceProfileCommand +} from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2InstanceProfileAttached 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 DescribeInstancesCommand({})) + + for (const reservation of response.Reservations || []) { + for (const instance of reservation.Instances || []) { + if (instance.IamInstanceProfile) { + compliantResources.push(instance.InstanceId!) + } else { + nonCompliantResources.push(instance.InstanceId!) + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'iam-instance-profile' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const iamInstanceProfile = requiredParametersForFix.find( + param => param.name === 'iam-instance-profile' + )?.value + + if (!iamInstanceProfile) { + throw new Error("Required parameter 'iam-instance-profile' is missing.") + } + + for (const instanceId of nonCompliantResources) { + await this.client.send( + new AssociateIamInstanceProfileCommand({ + InstanceId: instanceId, + IamInstanceProfile: { Name: iamInstanceProfile } + }) + ) + } + } +} diff --git a/src/bpsets/ec2/EC2NoAmazonKeyPair.ts b/src/bpsets/ec2/EC2NoAmazonKeyPair.ts new file mode 100644 index 0000000..7a9bd43 --- /dev/null +++ b/src/bpsets/ec2/EC2NoAmazonKeyPair.ts @@ -0,0 +1,39 @@ +import { + EC2Client, + DescribeInstancesCommand +} from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2NoAmazonKeyPair implements BPSet { + private readonly client = new EC2Client({}) + private readonly memoClient = Memorizer.memo(this.client) + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const response = await this.memoClient.send(new DescribeInstancesCommand({})) + + for (const reservation of response.Reservations || []) { + for (const instance of reservation.Instances || []) { + if (instance.KeyName) { + nonCompliantResources.push(instance.InstanceId!) + } else { + compliantResources.push(instance.InstanceId!) + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async () => { + throw new Error( + 'Fix logic for EC2NoAmazonKeyPair is not applicable. Key pairs must be removed manually or during instance creation.' + ) + } +} diff --git a/src/bpsets/ec2/EC2StoppedInstance.ts b/src/bpsets/ec2/EC2StoppedInstance.ts new file mode 100644 index 0000000..4e6707a --- /dev/null +++ b/src/bpsets/ec2/EC2StoppedInstance.ts @@ -0,0 +1,46 @@ +import { + EC2Client, + DescribeInstancesCommand, + TerminateInstancesCommand +} from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2StoppedInstance implements BPSet { + private readonly client = new EC2Client({}) + private readonly memoClient = Memorizer.memo(this.client) + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const response = await this.memoClient.send(new DescribeInstancesCommand({})) + + for (const reservation of response.Reservations || []) { + for (const instance of reservation.Instances || []) { + if (instance.State?.Name === 'stopped') { + nonCompliantResources.push(instance.InstanceId!) + } else { + compliantResources.push(instance.InstanceId!) + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + if (nonCompliantResources.length === 0) { + return // No stopped instances to terminate + } + + await this.client.send( + new TerminateInstancesCommand({ + InstanceIds: nonCompliantResources + }) + ) + } +} diff --git a/src/bpsets/ec2/EC2TokenHopLimitCheck.ts b/src/bpsets/ec2/EC2TokenHopLimitCheck.ts new file mode 100644 index 0000000..53467ce --- /dev/null +++ b/src/bpsets/ec2/EC2TokenHopLimitCheck.ts @@ -0,0 +1,48 @@ +import { + EC2Client, + DescribeInstancesCommand, + ModifyInstanceMetadataOptionsCommand +} from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EC2TokenHopLimitCheck implements BPSet { + private readonly client = new EC2Client({}) + private readonly memoClient = Memorizer.memo(this.client) + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const response = await this.memoClient.send(new DescribeInstancesCommand({})) + + for (const reservation of response.Reservations || []) { + for (const instance of reservation.Instances || []) { + if ( + instance.MetadataOptions?.HttpPutResponseHopLimit && + instance.MetadataOptions.HttpPutResponseHopLimit < 2 + ) { + compliantResources.push(instance.InstanceId!) + } else { + nonCompliantResources.push(instance.InstanceId!) + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const instanceId of nonCompliantResources) { + await this.client.send( + new ModifyInstanceMetadataOptionsCommand({ + InstanceId: instanceId, + HttpPutResponseHopLimit: 1 + }) + ) + } + } +} diff --git a/src/bpsets/ecr/ECRKmsEncryption1.ts b/src/bpsets/ecr/ECRKmsEncryption1.ts new file mode 100644 index 0000000..4c3d3ed --- /dev/null +++ b/src/bpsets/ecr/ECRKmsEncryption1.ts @@ -0,0 +1,99 @@ +import { + ECRClient, + DescribeRepositoriesCommand, + CreateRepositoryCommand, + ListImagesCommand, + BatchGetImageCommand, + PutImageCommand, + DeleteRepositoryCommand +} from '@aws-sdk/client-ecr' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECRKmsEncryption1 implements BPSet { + private readonly client = new ECRClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getRepositories = async () => { + const response = await this.memoClient.send(new DescribeRepositoriesCommand({})) + return response.repositories || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const repositories = await this.getRepositories() + + for (const repository of repositories) { + if (repository.encryptionConfiguration?.encryptionType === 'KMS') { + compliantResources.push(repository.repositoryArn!) + } else { + nonCompliantResources.push(repository.repositoryArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: '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) { + const repositoryName = arn.split('/').pop()! + + // Create a new repository with KMS encryption + const newRepositoryName = `${repositoryName}-kms` + await this.client.send( + new CreateRepositoryCommand({ + repositoryName: newRepositoryName, + encryptionConfiguration: { + encryptionType: 'KMS', + kmsKey: kmsKeyId + } + }) + ) + + // Get all images in the existing repository + const listImagesResponse = await this.client.send( + new ListImagesCommand({ repositoryName }) + ) + const imageIds = listImagesResponse.imageIds || [] + + if (imageIds.length > 0) { + const batchGetImageResponse = await this.client.send( + new BatchGetImageCommand({ repositoryName, imageIds }) + ) + + // Push images to the new repository + for (const image of batchGetImageResponse.images || []) { + await this.client.send( + new PutImageCommand({ + repositoryName: newRepositoryName, + imageManifest: image.imageManifest, + imageTag: image.imageId?.imageTag + }) + ) + } + } + + // Delete the old repository + await this.client.send( + new DeleteRepositoryCommand({ + repositoryName, + force: true + }) + ) + } + } +} diff --git a/src/bpsets/ecr/ECRPrivateImageScanningEnabled.ts b/src/bpsets/ecr/ECRPrivateImageScanningEnabled.ts new file mode 100644 index 0000000..b682db3 --- /dev/null +++ b/src/bpsets/ecr/ECRPrivateImageScanningEnabled.ts @@ -0,0 +1,50 @@ +import { + ECRClient, + DescribeRepositoriesCommand, + PutImageScanningConfigurationCommand +} from '@aws-sdk/client-ecr' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECRPrivateImageScanningEnabled implements BPSet { + private readonly client = new ECRClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getRepositories = async () => { + const response = await this.memoClient.send(new DescribeRepositoriesCommand({})) + return response.repositories || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const repositories = await this.getRepositories() + + for (const repository of repositories) { + if (repository.imageScanningConfiguration?.scanOnPush) { + compliantResources.push(repository.repositoryArn!) + } else { + nonCompliantResources.push(repository.repositoryArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const repositoryName = arn.split('/').pop()! + + await this.client.send( + new PutImageScanningConfigurationCommand({ + repositoryName, + imageScanningConfiguration: { scanOnPush: true } + }) + ) + } + } +} diff --git a/src/bpsets/ecr/ECRPrivateLifecyclePolicyConfigured.ts b/src/bpsets/ecr/ECRPrivateLifecyclePolicyConfigured.ts new file mode 100644 index 0000000..46ab532 --- /dev/null +++ b/src/bpsets/ecr/ECRPrivateLifecyclePolicyConfigured.ts @@ -0,0 +1,72 @@ +import { + ECRClient, + DescribeRepositoriesCommand, + PutLifecyclePolicyCommand, + GetLifecyclePolicyCommand +} from '@aws-sdk/client-ecr' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECRPrivateLifecyclePolicyConfigured implements BPSet { + private readonly client = new ECRClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getRepositories = async () => { + const response = await this.memoClient.send(new DescribeRepositoriesCommand({})) + return response.repositories || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const repositories = await this.getRepositories() + + for (const repository of repositories) { + try { + await this.client.send( + new GetLifecyclePolicyCommand({ + registryId: repository.registryId, + repositoryName: repository.repositoryName + }) + ) + compliantResources.push(repository.repositoryArn!) + } catch (error: any) { + if (error.name === 'LifecyclePolicyNotFoundException') { + nonCompliantResources.push(repository.repositoryArn!) + } else { + throw error + } + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'lifecycle-policy' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const lifecyclePolicy = requiredParametersForFix.find( + param => param.name === 'lifecycle-policy' + )?.value + + if (!lifecyclePolicy) { + throw new Error("Required parameter 'lifecycle-policy' is missing.") + } + + for (const arn of nonCompliantResources) { + const repositoryName = arn.split('/').pop()! + + await this.client.send( + new PutLifecyclePolicyCommand({ + repositoryName, + lifecyclePolicyText: lifecyclePolicy + }) + ) + } + } +} diff --git a/src/bpsets/ecr/ECRPrivateTagImmutabilityEnabled.ts b/src/bpsets/ecr/ECRPrivateTagImmutabilityEnabled.ts new file mode 100644 index 0000000..1c5ebd7 --- /dev/null +++ b/src/bpsets/ecr/ECRPrivateTagImmutabilityEnabled.ts @@ -0,0 +1,50 @@ +import { + ECRClient, + DescribeRepositoriesCommand, + PutImageTagMutabilityCommand +} from '@aws-sdk/client-ecr' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECRPrivateTagImmutabilityEnabled implements BPSet { + private readonly client = new ECRClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getRepositories = async () => { + const response = await this.memoClient.send(new DescribeRepositoriesCommand({})) + return response.repositories || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const repositories = await this.getRepositories() + + for (const repository of repositories) { + if (repository.imageTagMutability === 'IMMUTABLE') { + compliantResources.push(repository.repositoryArn!) + } else { + nonCompliantResources.push(repository.repositoryArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const repositoryName = arn.split('/').pop()! + + await this.client.send( + new PutImageTagMutabilityCommand({ + repositoryName, + imageTagMutability: 'IMMUTABLE' + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSAwsVpcNetworkingEnabled.ts b/src/bpsets/ecs/ECSAwsVpcNetworkingEnabled.ts new file mode 100644 index 0000000..bb717f0 --- /dev/null +++ b/src/bpsets/ecs/ECSAwsVpcNetworkingEnabled.ts @@ -0,0 +1,67 @@ +import { + ECSClient, + ListTaskDefinitionsCommand, + DescribeTaskDefinitionCommand, + RegisterTaskDefinitionCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSAwsVpcNetworkingEnabled implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTaskDefinitions = async () => { + const taskDefinitionArns = await this.memoClient.send( + new ListTaskDefinitionsCommand({ status: 'ACTIVE' }) + ) + const taskDefinitions = [] + for (const arn of taskDefinitionArns.taskDefinitionArns || []) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + taskDefinitions.push(taskDefinition.taskDefinition!) + } + return taskDefinitions + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const taskDefinitions = await this.getTaskDefinitions() + + for (const taskDefinition of taskDefinitions) { + if (taskDefinition.networkMode === 'awsvpc') { + compliantResources.push(taskDefinition.taskDefinitionArn!) + } else { + nonCompliantResources.push(taskDefinition.taskDefinitionArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + const family = taskDefinition.taskDefinition?.family + + await this.client.send( + new RegisterTaskDefinitionCommand({ + family, + containerDefinitions: taskDefinition.taskDefinition?.containerDefinitions, + networkMode: 'awsvpc', + requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities, + cpu: taskDefinition.taskDefinition?.cpu, + memory: taskDefinition.taskDefinition?.memory + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSContainerInsightsEnabled.ts b/src/bpsets/ecs/ECSContainerInsightsEnabled.ts new file mode 100644 index 0000000..cab3436 --- /dev/null +++ b/src/bpsets/ecs/ECSContainerInsightsEnabled.ts @@ -0,0 +1,51 @@ +import { + ECSClient, + DescribeClustersCommand, + UpdateClusterSettingsCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSContainerInsightsEnabled implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getClusters = async () => { + const response = await this.memoClient.send(new DescribeClustersCommand({ include: ['SETTINGS'] })) + return response.clusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const clusters = await this.getClusters() + + for (const cluster of clusters) { + const containerInsightsSetting = cluster.settings?.find( + setting => setting.name === 'containerInsights' + ) + if (containerInsightsSetting?.value === 'enabled') { + compliantResources.push(cluster.clusterArn!) + } else { + nonCompliantResources.push(cluster.clusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + await this.client.send( + new UpdateClusterSettingsCommand({ + cluster: arn, + settings: [{ name: 'containerInsights', value: 'enabled' }] + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSContainersNonPrivileged.ts b/src/bpsets/ecs/ECSContainersNonPrivileged.ts new file mode 100644 index 0000000..0cb413a --- /dev/null +++ b/src/bpsets/ecs/ECSContainersNonPrivileged.ts @@ -0,0 +1,77 @@ +import { + ECSClient, + DescribeTaskDefinitionCommand, + RegisterTaskDefinitionCommand, + ListTaskDefinitionsCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSContainersNonPrivileged implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTaskDefinitions = async () => { + const taskDefinitionArns = await this.memoClient.send( + new ListTaskDefinitionsCommand({ status: 'ACTIVE' }) + ) + const taskDefinitions = [] + for (const arn of taskDefinitionArns.taskDefinitionArns || []) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + taskDefinitions.push(taskDefinition.taskDefinition!) + } + return taskDefinitions + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const taskDefinitions = await this.getTaskDefinitions() + + for (const taskDefinition of taskDefinitions) { + const privilegedContainers = taskDefinition.containerDefinitions?.filter( + container => container.privileged + ) + if (privilegedContainers?.length) { + nonCompliantResources.push(taskDefinition.taskDefinitionArn!) + } else { + compliantResources.push(taskDefinition.taskDefinitionArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + const family = taskDefinition.taskDefinition?.family + + const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map( + container => ({ + ...container, + privileged: false + }) + ) + + await this.client.send( + new RegisterTaskDefinitionCommand({ + family, + containerDefinitions: updatedContainers, + networkMode: taskDefinition.taskDefinition?.networkMode, + requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities, + cpu: taskDefinition.taskDefinition?.cpu, + memory: taskDefinition.taskDefinition?.memory + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSContainersReadonlyAccess.ts b/src/bpsets/ecs/ECSContainersReadonlyAccess.ts new file mode 100644 index 0000000..70fe0ad --- /dev/null +++ b/src/bpsets/ecs/ECSContainersReadonlyAccess.ts @@ -0,0 +1,77 @@ +import { + ECSClient, + DescribeTaskDefinitionCommand, + RegisterTaskDefinitionCommand, + ListTaskDefinitionsCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSContainersReadonlyAccess implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTaskDefinitions = async () => { + const taskDefinitionArns = await this.memoClient.send( + new ListTaskDefinitionsCommand({ status: 'ACTIVE' }) + ) + const taskDefinitions = [] + for (const arn of taskDefinitionArns.taskDefinitionArns || []) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + taskDefinitions.push(taskDefinition.taskDefinition!) + } + return taskDefinitions + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const taskDefinitions = await this.getTaskDefinitions() + + for (const taskDefinition of taskDefinitions) { + const notReadonlyContainers = taskDefinition.containerDefinitions?.filter( + container => !container.readonlyRootFilesystem + ) + if (notReadonlyContainers?.length) { + nonCompliantResources.push(taskDefinition.taskDefinitionArn!) + } else { + compliantResources.push(taskDefinition.taskDefinitionArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + const family = taskDefinition.taskDefinition?.family + + const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map( + container => ({ + ...container, + readonlyRootFilesystem: true + }) + ) + + await this.client.send( + new RegisterTaskDefinitionCommand({ + family, + containerDefinitions: updatedContainers, + networkMode: taskDefinition.taskDefinition?.networkMode, + requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities, + cpu: taskDefinition.taskDefinition?.cpu, + memory: taskDefinition.taskDefinition?.memory + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSFargateLatestPlatformVersion.ts b/src/bpsets/ecs/ECSFargateLatestPlatformVersion.ts new file mode 100644 index 0000000..d219b9b --- /dev/null +++ b/src/bpsets/ecs/ECSFargateLatestPlatformVersion.ts @@ -0,0 +1,70 @@ +import { + ECSClient, + ListClustersCommand, + ListServicesCommand, + DescribeServicesCommand, + UpdateServiceCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSFargateLatestPlatformVersion implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getServices = async () => { + const clustersResponse = await this.memoClient.send(new ListClustersCommand({})) + const clusterArns = clustersResponse.clusterArns || [] + const services: { clusterArn: string; serviceArn: string }[] = [] + + for (const clusterArn of clusterArns) { + const servicesResponse = await this.memoClient.send( + new ListServicesCommand({ cluster: clusterArn }) + ) + for (const serviceArn of servicesResponse.serviceArns || []) { + services.push({ clusterArn, serviceArn }) + } + } + + return services + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const services = await this.getServices() + + for (const { clusterArn, serviceArn } of services) { + const serviceResponse = await this.memoClient.send( + new DescribeServicesCommand({ cluster: clusterArn, services: [serviceArn] }) + ) + + const service = serviceResponse.services?.[0] + if (service?.platformVersion === 'LATEST') { + compliantResources.push(service.serviceArn!) + } else { + nonCompliantResources.push(service?.serviceArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const serviceArn of nonCompliantResources) { + const clusterArn = serviceArn.split(':cluster/')[1].split(':service/')[0] + + await this.client.send( + new UpdateServiceCommand({ + cluster: clusterArn, + service: serviceArn, + platformVersion: 'LATEST' + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSTaskDefinitionLogConfiguration.ts b/src/bpsets/ecs/ECSTaskDefinitionLogConfiguration.ts new file mode 100644 index 0000000..d4a3e65 --- /dev/null +++ b/src/bpsets/ecs/ECSTaskDefinitionLogConfiguration.ts @@ -0,0 +1,88 @@ +import { + ECSClient, + ListTaskDefinitionsCommand, + DescribeTaskDefinitionCommand, + RegisterTaskDefinitionCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSTaskDefinitionLogConfiguration implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTaskDefinitions = async () => { + const taskDefinitionArns = await this.memoClient.send( + new ListTaskDefinitionsCommand({ status: 'ACTIVE' }) + ) + const taskDefinitions = [] + for (const arn of taskDefinitionArns.taskDefinitionArns || []) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + taskDefinitions.push(taskDefinition.taskDefinition!) + } + return taskDefinitions + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const taskDefinitions = await this.getTaskDefinitions() + + for (const taskDefinition of taskDefinitions) { + const logDisabledContainers = taskDefinition.containerDefinitions?.filter( + container => !container.logConfiguration + ) + if (logDisabledContainers?.length) { + nonCompliantResources.push(taskDefinition.taskDefinitionArn!) + } else { + compliantResources.push(taskDefinition.taskDefinitionArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'log-configuration' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const logConfiguration = requiredParametersForFix.find( + param => param.name === 'log-configuration' + )?.value + + if (!logConfiguration) { + throw new Error("Required parameter 'log-configuration' is missing.") + } + + for (const arn of nonCompliantResources) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + const family = taskDefinition.taskDefinition?.family + + const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map( + container => ({ + ...container, + logConfiguration: JSON.parse(logConfiguration) + }) + ) + + await this.client.send( + new RegisterTaskDefinitionCommand({ + family, + containerDefinitions: updatedContainers, + networkMode: taskDefinition.taskDefinition?.networkMode, + requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities, + cpu: taskDefinition.taskDefinition?.cpu, + memory: taskDefinition.taskDefinition?.memory + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSTaskDefinitionMemoryHardLimit.ts b/src/bpsets/ecs/ECSTaskDefinitionMemoryHardLimit.ts new file mode 100644 index 0000000..ea30024 --- /dev/null +++ b/src/bpsets/ecs/ECSTaskDefinitionMemoryHardLimit.ts @@ -0,0 +1,77 @@ +import { + ECSClient, + ListTaskDefinitionsCommand, + DescribeTaskDefinitionCommand, + RegisterTaskDefinitionCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSTaskDefinitionMemoryHardLimit implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTaskDefinitions = async () => { + const taskDefinitionArns = await this.memoClient.send( + new ListTaskDefinitionsCommand({ status: 'ACTIVE' }) + ) + const taskDefinitions = [] + for (const arn of taskDefinitionArns.taskDefinitionArns || []) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + taskDefinitions.push(taskDefinition.taskDefinition!) + } + return taskDefinitions + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const taskDefinitions = await this.getTaskDefinitions() + + for (const taskDefinition of taskDefinitions) { + const containersWithoutMemoryLimit = taskDefinition.containerDefinitions?.filter( + container => !container.memory + ) + if (containersWithoutMemoryLimit?.length) { + nonCompliantResources.push(taskDefinition.taskDefinitionArn!) + } else { + compliantResources.push(taskDefinition.taskDefinitionArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + const family = taskDefinition.taskDefinition?.family + + const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map( + container => ({ + ...container, + memory: container.memory || 512 // Default hard limit memory value + }) + ) + + await this.client.send( + new RegisterTaskDefinitionCommand({ + family, + containerDefinitions: updatedContainers, + networkMode: taskDefinition.taskDefinition?.networkMode, + requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities, + cpu: taskDefinition.taskDefinition?.cpu, + memory: taskDefinition.taskDefinition?.memory + }) + ) + } + } +} diff --git a/src/bpsets/ecs/ECSTaskDefinitionNonRootUser.ts b/src/bpsets/ecs/ECSTaskDefinitionNonRootUser.ts new file mode 100644 index 0000000..1d81f83 --- /dev/null +++ b/src/bpsets/ecs/ECSTaskDefinitionNonRootUser.ts @@ -0,0 +1,77 @@ +import { + ECSClient, + ListTaskDefinitionsCommand, + DescribeTaskDefinitionCommand, + RegisterTaskDefinitionCommand +} from '@aws-sdk/client-ecs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ECSTaskDefinitionNonRootUser implements BPSet { + private readonly client = new ECSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getTaskDefinitions = async () => { + const taskDefinitionArns = await this.memoClient.send( + new ListTaskDefinitionsCommand({ status: 'ACTIVE' }) + ) + const taskDefinitions = [] + for (const arn of taskDefinitionArns.taskDefinitionArns || []) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + taskDefinitions.push(taskDefinition.taskDefinition!) + } + return taskDefinitions + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const taskDefinitions = await this.getTaskDefinitions() + + for (const taskDefinition of taskDefinitions) { + const privilegedContainers = taskDefinition.containerDefinitions?.filter( + container => !container.user || container.user === 'root' + ) + if (privilegedContainers?.length) { + nonCompliantResources.push(taskDefinition.taskDefinitionArn!) + } else { + compliantResources.push(taskDefinition.taskDefinitionArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const taskDefinition = await this.memoClient.send( + new DescribeTaskDefinitionCommand({ taskDefinition: arn }) + ) + const family = taskDefinition.taskDefinition?.family + + const updatedContainers = taskDefinition.taskDefinition?.containerDefinitions?.map( + container => ({ + ...container, + user: container.user || 'ecs-user' // Default non-root user + }) + ) + + await this.client.send( + new RegisterTaskDefinitionCommand({ + family, + containerDefinitions: updatedContainers, + networkMode: taskDefinition.taskDefinition?.networkMode, + requiresCompatibilities: taskDefinition.taskDefinition?.requiresCompatibilities, + cpu: taskDefinition.taskDefinition?.cpu, + memory: taskDefinition.taskDefinition?.memory + }) + ) + } + } +} diff --git a/src/bpsets/efs/EFSAccessPointEnforceRootDirectory.ts b/src/bpsets/efs/EFSAccessPointEnforceRootDirectory.ts new file mode 100644 index 0000000..03ee052 --- /dev/null +++ b/src/bpsets/efs/EFSAccessPointEnforceRootDirectory.ts @@ -0,0 +1,71 @@ +import { + EFSClient, + DescribeAccessPointsCommand, + DeleteAccessPointCommand, + CreateAccessPointCommand +} from '@aws-sdk/client-efs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EFSAccessPointEnforceRootDirectory implements BPSet { + private readonly client = new EFSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getAccessPoints = async () => { + const response = await this.memoClient.send(new DescribeAccessPointsCommand({})) + return response.AccessPoints || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const accessPoints = await this.getAccessPoints() + + for (const accessPoint of accessPoints) { + if (accessPoint.RootDirectory?.Path !== '/') { + compliantResources.push(accessPoint.AccessPointArn!) + } else { + nonCompliantResources.push(accessPoint.AccessPointArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'root-directory-path' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const rootDirectoryPath = requiredParametersForFix.find( + param => param.name === 'root-directory-path' + )?.value + + if (!rootDirectoryPath) { + throw new Error("Required parameter 'root-directory-path' is missing.") + } + + for (const arn of nonCompliantResources) { + const accessPointId = arn.split('/').pop()! + const fileSystemId = arn.split(':file-system/')[1].split('/')[0] + + // Delete the existing access point + await this.client.send( + new DeleteAccessPointCommand({ + AccessPointId: accessPointId + }) + ) + + // Recreate the access point with the desired root directory + await this.client.send( + new CreateAccessPointCommand({ + FileSystemId: fileSystemId, + RootDirectory: { Path: rootDirectoryPath } + }) + ) + } + } +} diff --git a/src/bpsets/efs/EFSAccessPointEnforceUserIdentity.ts b/src/bpsets/efs/EFSAccessPointEnforceUserIdentity.ts new file mode 100644 index 0000000..421e06f --- /dev/null +++ b/src/bpsets/efs/EFSAccessPointEnforceUserIdentity.ts @@ -0,0 +1,69 @@ +import { + EFSClient, + DescribeAccessPointsCommand, + DeleteAccessPointCommand, + CreateAccessPointCommand +} from '@aws-sdk/client-efs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EFSAccessPointEnforceUserIdentity implements BPSet { + private readonly client = new EFSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getAccessPoints = async () => { + const response = await this.memoClient.send(new DescribeAccessPointsCommand({})) + return response.AccessPoints || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const accessPoints = await this.getAccessPoints() + + for (const accessPoint of accessPoints) { + if (accessPoint.PosixUser) { + compliantResources.push(accessPoint.AccessPointArn!) + } else { + nonCompliantResources.push(accessPoint.AccessPointArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'posix-user' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const posixUser = requiredParametersForFix.find(param => param.name === 'posix-user')?.value + + if (!posixUser) { + throw new Error("Required parameter 'posix-user' is missing.") + } + + for (const arn of nonCompliantResources) { + const accessPointId = arn.split('/').pop()! + const fileSystemId = arn.split(':file-system/')[1].split('/')[0] + + // Delete the existing access point + await this.client.send( + new DeleteAccessPointCommand({ + AccessPointId: accessPointId + }) + ) + + // Recreate the access point with the desired PosixUser + await this.client.send( + new CreateAccessPointCommand({ + FileSystemId: fileSystemId, + PosixUser: JSON.parse(posixUser) + }) + ) + } + } +} diff --git a/src/bpsets/efs/EFSAutomaticBackupsEnabled.ts b/src/bpsets/efs/EFSAutomaticBackupsEnabled.ts new file mode 100644 index 0000000..7500671 --- /dev/null +++ b/src/bpsets/efs/EFSAutomaticBackupsEnabled.ts @@ -0,0 +1,55 @@ +import { + EFSClient, + DescribeFileSystemsCommand, + PutBackupPolicyCommand, + DescribeBackupPolicyCommand +} from '@aws-sdk/client-efs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EFSAutomaticBackupsEnabled implements BPSet { + private readonly client = new EFSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getFileSystems = async () => { + const response = await this.memoClient.send(new DescribeFileSystemsCommand({})) + return response.FileSystems || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const fileSystems = await this.getFileSystems() + + for (const fileSystem of fileSystems) { + const response = await this.client.send( + new DescribeBackupPolicyCommand({ FileSystemId: fileSystem.FileSystemId! }) + ) + + if (response.BackupPolicy?.Status === 'ENABLED') { + compliantResources.push(fileSystem.FileSystemArn!) + } else { + nonCompliantResources.push(fileSystem.FileSystemArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const fileSystemId = arn.split('/').pop()! + + await this.client.send( + new PutBackupPolicyCommand({ + FileSystemId: fileSystemId, + BackupPolicy: { Status: 'ENABLED' } + }) + ) + } + } +} diff --git a/src/bpsets/efs/EFSEncryptedCheck.ts b/src/bpsets/efs/EFSEncryptedCheck.ts new file mode 100644 index 0000000..78cd514 --- /dev/null +++ b/src/bpsets/efs/EFSEncryptedCheck.ts @@ -0,0 +1,64 @@ +import { + EFSClient, + DescribeFileSystemsCommand, + CreateFileSystemCommand, + DeleteFileSystemCommand +} from '@aws-sdk/client-efs' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EFSEncryptedCheck implements BPSet { + private readonly client = new EFSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getFileSystems = async () => { + const response = await this.memoClient.send(new DescribeFileSystemsCommand({})) + return response.FileSystems || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const fileSystems = await this.getFileSystems() + + for (const fileSystem of fileSystems) { + if (fileSystem.Encrypted) { + compliantResources.push(fileSystem.FileSystemArn!) + } else { + nonCompliantResources.push(fileSystem.FileSystemArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const fileSystemId = arn.split('/').pop()! + const fileSystem = await this.memoClient.send( + new DescribeFileSystemsCommand({ FileSystemId: fileSystemId }) + ) + + // Delete the non-compliant file system + await this.client.send( + new DeleteFileSystemCommand({ + FileSystemId: fileSystemId + }) + ) + + // Recreate the file system with encryption enabled + await this.client.send( + new CreateFileSystemCommand({ + Encrypted: true, + PerformanceMode: fileSystem.FileSystems?.[0]?.PerformanceMode, + ThroughputMode: fileSystem.FileSystems?.[0]?.ThroughputMode, + ProvisionedThroughputInMibps: fileSystem.FileSystems?.[0]?.ProvisionedThroughputInMibps + }) + ) + } + } +} diff --git a/src/bpsets/efs/EFSMountTargetPublicAccessible.ts b/src/bpsets/efs/EFSMountTargetPublicAccessible.ts new file mode 100644 index 0000000..e523aea --- /dev/null +++ b/src/bpsets/efs/EFSMountTargetPublicAccessible.ts @@ -0,0 +1,71 @@ +import { + EFSClient, + DescribeFileSystemsCommand, + DescribeMountTargetsCommand +} from '@aws-sdk/client-efs' +import { EC2Client, DescribeRouteTablesCommand } from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EFSMountTargetPublicAccessible implements BPSet { + private readonly efsClient = new EFSClient({}) + private readonly ec2Client = new EC2Client({}) + private readonly memoEFSClient = Memorizer.memo(this.efsClient) + private readonly memoEC2Client = Memorizer.memo(this.ec2Client) + + private readonly getFileSystems = async () => { + const response = await this.memoEFSClient.send(new DescribeFileSystemsCommand({})) + return response.FileSystems || [] + } + + private readonly getRoutesForSubnet = async (subnetId: string) => { + const response = await this.memoEC2Client.send( + new DescribeRouteTablesCommand({ + Filters: [{ Name: 'association.subnet-id', Values: [subnetId] }] + }) + ) + return response.RouteTables?.[0]?.Routes || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const fileSystems = await this.getFileSystems() + + for (const fileSystem of fileSystems) { + const mountTargets = await this.memoEFSClient.send( + new DescribeMountTargetsCommand({ FileSystemId: fileSystem.FileSystemId! }) + ) + + for (const mountTarget of mountTargets.MountTargets || []) { + const routes = await this.getRoutesForSubnet(mountTarget.SubnetId!) + + for (const route of routes) { + if ( + route.DestinationCidrBlock === '0.0.0.0/0' && + route.GatewayId?.startsWith('igw-') + ) { + nonCompliantResources.push(fileSystem.FileSystemArn!) + break + } + } + } + + if (!nonCompliantResources.includes(fileSystem.FileSystemArn!)) { + compliantResources.push(fileSystem.FileSystemArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + throw new Error( + 'Fixing public accessibility for mount targets requires manual network reconfiguration.' + ) + } +} diff --git a/src/bpsets/eks/EKSClusterLoggingEnabled.ts b/src/bpsets/eks/EKSClusterLoggingEnabled.ts new file mode 100644 index 0000000..06c6be4 --- /dev/null +++ b/src/bpsets/eks/EKSClusterLoggingEnabled.ts @@ -0,0 +1,67 @@ +import { + EKSClient, + ListClustersCommand, + DescribeClusterCommand, + UpdateClusterConfigCommand +} from '@aws-sdk/client-eks' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EKSClusterLoggingEnabled implements BPSet { + private readonly client = new EKSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getClusters = async () => { + const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({})) + const clusterNames = clusterNamesResponse.clusters || [] + const clusters = [] + for (const clusterName of clusterNames) { + const cluster = await this.memoClient.send( + new DescribeClusterCommand({ name: clusterName }) + ) + clusters.push(cluster.cluster!) + } + return clusters + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const clusters = await this.getClusters() + + for (const cluster of clusters) { + const clusterLogging = cluster.logging?.clusterLogging?.[0] + if (clusterLogging?.enabled && clusterLogging.types?.length === 5) { + compliantResources.push(cluster.arn!) + } else { + nonCompliantResources.push(cluster.arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterName = arn.split(':cluster/')[1] + + await this.client.send( + new UpdateClusterConfigCommand({ + name: clusterName, + logging: { + clusterLogging: [ + { + enabled: true, + types: ['api', 'audit', 'authenticator', 'controllerManager', 'scheduler'] + } + ] + } + }) + ) + } + } +} diff --git a/src/bpsets/eks/EKSClusterSecretsEncrypted.ts b/src/bpsets/eks/EKSClusterSecretsEncrypted.ts new file mode 100644 index 0000000..9587a0a --- /dev/null +++ b/src/bpsets/eks/EKSClusterSecretsEncrypted.ts @@ -0,0 +1,74 @@ +import { + EKSClient, + ListClustersCommand, + DescribeClusterCommand, + AssociateEncryptionConfigCommand +} from '@aws-sdk/client-eks' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EKSClusterSecretsEncrypted implements BPSet { + private readonly client = new EKSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getClusters = async () => { + const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({})) + const clusterNames = clusterNamesResponse.clusters || [] + const clusters = [] + for (const clusterName of clusterNames) { + const cluster = await this.memoClient.send( + new DescribeClusterCommand({ name: clusterName }) + ) + clusters.push(cluster.cluster!) + } + return clusters + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const clusters = await this.getClusters() + + for (const cluster of clusters) { + const encryptionConfig = cluster.encryptionConfig?.[0] + if (encryptionConfig?.resources?.includes('secrets')) { + compliantResources.push(cluster.arn!) + } else { + nonCompliantResources.push(cluster.arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: '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) { + const clusterName = arn.split(':cluster/')[1] + + await this.client.send( + new AssociateEncryptionConfigCommand({ + clusterName, + encryptionConfig: [ + { + resources: ['secrets'], + provider: { keyArn: kmsKeyId } + } + ] + }) + ) + } + } +} diff --git a/src/bpsets/eks/EKSEndpointNoPublicAccess.ts b/src/bpsets/eks/EKSEndpointNoPublicAccess.ts new file mode 100644 index 0000000..0be846a --- /dev/null +++ b/src/bpsets/eks/EKSEndpointNoPublicAccess.ts @@ -0,0 +1,62 @@ +import { + EKSClient, + ListClustersCommand, + DescribeClusterCommand, + UpdateClusterConfigCommand +} from '@aws-sdk/client-eks' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class EKSEndpointNoPublicAccess implements BPSet { + private readonly client = new EKSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getClusters = async () => { + const clusterNamesResponse = await this.memoClient.send(new ListClustersCommand({})) + const clusterNames = clusterNamesResponse.clusters || [] + const clusters = [] + for (const clusterName of clusterNames) { + const cluster = await this.memoClient.send( + new DescribeClusterCommand({ name: clusterName }) + ) + clusters.push(cluster.cluster!) + } + return clusters + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const clusters = await this.getClusters() + + for (const cluster of clusters) { + const endpointPublicAccess = cluster.resourcesVpcConfig?.endpointPublicAccess + if (endpointPublicAccess) { + nonCompliantResources.push(cluster.arn!) + } else { + compliantResources.push(cluster.arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterName = arn.split(':cluster/')[1] + + await this.client.send( + new UpdateClusterConfigCommand({ + name: clusterName, + resourcesVpcConfig: { + endpointPublicAccess: false + } + }) + ) + } + } +} diff --git a/src/bpsets/elasticache/ElastiCacheAutoMinorVersionUpgradeCheck.ts b/src/bpsets/elasticache/ElastiCacheAutoMinorVersionUpgradeCheck.ts new file mode 100644 index 0000000..3b95d79 --- /dev/null +++ b/src/bpsets/elasticache/ElastiCacheAutoMinorVersionUpgradeCheck.ts @@ -0,0 +1,49 @@ +import { + ElastiCacheClient, + DescribeCacheClustersCommand, + ModifyCacheClusterCommand +} from '@aws-sdk/client-elasticache' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ElastiCacheAutoMinorVersionUpgradeCheck implements BPSet { + private readonly client = new ElastiCacheClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getClusters = async () => { + const response = await this.memoClient.send(new DescribeCacheClustersCommand({})) + return response.CacheClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const clusters = await this.getClusters() + + for (const cluster of clusters) { + if (cluster.AutoMinorVersionUpgrade) { + compliantResources.push(cluster.ARN!) + } else { + nonCompliantResources.push(cluster.ARN!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster:')[1] + await this.client.send( + new ModifyCacheClusterCommand({ + CacheClusterId: clusterId, + AutoMinorVersionUpgrade: true + }) + ) + } + } +} diff --git a/src/bpsets/elasticache/ElastiCacheRedisClusterAutomaticBackupCheck.ts b/src/bpsets/elasticache/ElastiCacheRedisClusterAutomaticBackupCheck.ts new file mode 100644 index 0000000..19c7738 --- /dev/null +++ b/src/bpsets/elasticache/ElastiCacheRedisClusterAutomaticBackupCheck.ts @@ -0,0 +1,60 @@ +import { + ElastiCacheClient, + DescribeReplicationGroupsCommand, + ModifyReplicationGroupCommand +} from '@aws-sdk/client-elasticache' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ElastiCacheRedisClusterAutomaticBackupCheck implements BPSet { + private readonly client = new ElastiCacheClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getReplicationGroups = async () => { + const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({})) + return response.ReplicationGroups || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const replicationGroups = await this.getReplicationGroups() + + for (const group of replicationGroups) { + if (group.SnapshottingClusterId) { + compliantResources.push(group.ARN!) + } else { + nonCompliantResources.push(group.ARN!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'snapshot-retention-period' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const retentionPeriod = requiredParametersForFix.find( + param => param.name === 'snapshot-retention-period' + )?.value + + if (!retentionPeriod) { + throw new Error("Required parameter 'snapshot-retention-period' is missing.") + } + + for (const arn of nonCompliantResources) { + const groupId = arn.split(':replication-group:')[1] + await this.client.send( + new ModifyReplicationGroupCommand({ + ReplicationGroupId: groupId, + SnapshotRetentionLimit: parseInt(retentionPeriod, 10) + }) + ) + } + } +} diff --git a/src/bpsets/elasticache/ElastiCacheReplGrpAutoFailoverEnabled.ts b/src/bpsets/elasticache/ElastiCacheReplGrpAutoFailoverEnabled.ts new file mode 100644 index 0000000..99a6d8e --- /dev/null +++ b/src/bpsets/elasticache/ElastiCacheReplGrpAutoFailoverEnabled.ts @@ -0,0 +1,49 @@ +import { + ElastiCacheClient, + DescribeReplicationGroupsCommand, + ModifyReplicationGroupCommand +} from '@aws-sdk/client-elasticache' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ElastiCacheReplGrpAutoFailoverEnabled implements BPSet { + private readonly client = new ElastiCacheClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getReplicationGroups = async () => { + const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({})) + return response.ReplicationGroups || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const replicationGroups = await this.getReplicationGroups() + + for (const group of replicationGroups) { + if (group.AutomaticFailover === 'enabled') { + compliantResources.push(group.ARN!) + } else { + nonCompliantResources.push(group.ARN!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const groupId = arn.split(':replication-group:')[1] + await this.client.send( + new ModifyReplicationGroupCommand({ + ReplicationGroupId: groupId, + AutomaticFailoverEnabled: true + }) + ) + } + } +} diff --git a/src/bpsets/elasticache/ElastiCacheReplGrpEncryptedAtRest.ts b/src/bpsets/elasticache/ElastiCacheReplGrpEncryptedAtRest.ts new file mode 100644 index 0000000..95ccf47 --- /dev/null +++ b/src/bpsets/elasticache/ElastiCacheReplGrpEncryptedAtRest.ts @@ -0,0 +1,43 @@ +import { + ElastiCacheClient, + DescribeReplicationGroupsCommand, + ModifyReplicationGroupCommand +} from '@aws-sdk/client-elasticache' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ElastiCacheReplGrpEncryptedAtRest implements BPSet { + private readonly client = new ElastiCacheClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getReplicationGroups = async () => { + const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({})) + return response.ReplicationGroups || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const replicationGroups = await this.getReplicationGroups() + + for (const group of replicationGroups) { + if (group.AtRestEncryptionEnabled) { + compliantResources.push(group.ARN!) + } else { + nonCompliantResources.push(group.ARN!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + throw new Error( + 'Fixing encryption at rest for replication groups requires recreation. Please create a new replication group with AtRestEncryptionEnabled set to true.' + ) + } +} diff --git a/src/bpsets/elasticache/ElastiCacheReplGrpEncryptedInTransit.ts b/src/bpsets/elasticache/ElastiCacheReplGrpEncryptedInTransit.ts new file mode 100644 index 0000000..9c8cd35 --- /dev/null +++ b/src/bpsets/elasticache/ElastiCacheReplGrpEncryptedInTransit.ts @@ -0,0 +1,43 @@ +import { + ElastiCacheClient, + DescribeReplicationGroupsCommand, + ModifyReplicationGroupCommand +} from '@aws-sdk/client-elasticache' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ElastiCacheReplGrpEncryptedInTransit implements BPSet { + private readonly client = new ElastiCacheClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getReplicationGroups = async () => { + const response = await this.memoClient.send(new DescribeReplicationGroupsCommand({})) + return response.ReplicationGroups || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const replicationGroups = await this.getReplicationGroups() + + for (const group of replicationGroups) { + if (group.TransitEncryptionEnabled) { + compliantResources.push(group.ARN!) + } else { + nonCompliantResources.push(group.ARN!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + throw new Error( + 'Fixing in-transit encryption for replication groups requires recreation. Please create a new replication group with TransitEncryptionEnabled set to true.' + ) + } +} diff --git a/src/bpsets/elasticache/ElastiCacheSubnetGroupCheck.ts b/src/bpsets/elasticache/ElastiCacheSubnetGroupCheck.ts new file mode 100644 index 0000000..8f2a08d --- /dev/null +++ b/src/bpsets/elasticache/ElastiCacheSubnetGroupCheck.ts @@ -0,0 +1,84 @@ +import { + ElastiCacheClient, + DescribeCacheClustersCommand, + DeleteCacheClusterCommand, + CreateCacheClusterCommand +} from '@aws-sdk/client-elasticache' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class ElastiCacheSubnetGroupCheck implements BPSet { + private readonly client = new ElastiCacheClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getClusters = async () => { + const response = await this.memoClient.send(new DescribeCacheClustersCommand({})) + return response.CacheClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const clusters = await this.getClusters() + + for (const cluster of clusters) { + if (cluster.CacheSubnetGroupName !== 'default') { + compliantResources.push(cluster.ARN!) + } else { + nonCompliantResources.push(cluster.ARN!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'subnet-group-name' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const subnetGroupName = requiredParametersForFix.find( + param => param.name === 'subnet-group-name' + )?.value + + if (!subnetGroupName) { + throw new Error("Required parameter 'subnet-group-name' is missing.") + } + + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster:')[1] + const cluster = await this.memoClient.send( + new DescribeCacheClustersCommand({ CacheClusterId: clusterId }) + ) + const clusterDetails = cluster.CacheClusters?.[0] + + if (!clusterDetails) { + continue + } + + // Delete the non-compliant cluster + await this.client.send( + new DeleteCacheClusterCommand({ + CacheClusterId: clusterId + }) + ) + + // Recreate the cluster with the desired subnet group + await this.client.send( + new CreateCacheClusterCommand({ + CacheClusterId: clusterDetails.CacheClusterId!, + Engine: clusterDetails.Engine!, + CacheNodeType: clusterDetails.CacheNodeType!, + NumCacheNodes: clusterDetails.NumCacheNodes!, + CacheSubnetGroupName: subnetGroupName, + SecurityGroupIds: clusterDetails.SecurityGroups?.map(group => group.SecurityGroupId) as string[], + PreferredMaintenanceWindow: clusterDetails.PreferredMaintenanceWindow, + EngineVersion: clusterDetails.EngineVersion + }) + ) + } + } +} diff --git a/src/bpsets/iam/IAMPolicyNoStatementsWithAdminAccess.ts b/src/bpsets/iam/IAMPolicyNoStatementsWithAdminAccess.ts new file mode 100644 index 0000000..ef8c524 --- /dev/null +++ b/src/bpsets/iam/IAMPolicyNoStatementsWithAdminAccess.ts @@ -0,0 +1,67 @@ +import { + IAMClient, + ListPoliciesCommand, + GetPolicyVersionCommand, + DeletePolicyCommand +} from '@aws-sdk/client-iam' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class IAMPolicyNoStatementsWithAdminAccess implements BPSet { + private readonly client = new IAMClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getPolicies = async () => { + const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' })) + return response.Policies || [] + } + + private readonly getPolicyDefaultVersions = async (policyArn: string, versionId: string) => { + const response = await this.memoClient.send( + new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: versionId }) + ) + return response.PolicyVersion! + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const policies = await this.getPolicies() + + for (const policy of policies) { + const policyVersion = await this.getPolicyDefaultVersions(policy.Arn!, policy.DefaultVersionId!) + + const policyDocument = JSON.parse(JSON.stringify(policyVersion.Document)) // Parse Document JSON string + const statements = Array.isArray(policyDocument.Statement) + ? policyDocument.Statement + : [policyDocument.Statement] + + for (const statement of statements) { + if ( + statement.Action === '*' && + statement.Resource === '*' && + statement.Effect === 'Allow' + ) { + nonCompliantResources.push(policy.Arn!) + break + } + } + + if (!nonCompliantResources.includes(policy.Arn!)) { + compliantResources.push(policy.Arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + await this.client.send(new DeletePolicyCommand({ PolicyArn: arn })) + } + } +} diff --git a/src/bpsets/iam/IAMPolicyNoStatementsWithFullAccess.ts b/src/bpsets/iam/IAMPolicyNoStatementsWithFullAccess.ts new file mode 100644 index 0000000..ef8c524 --- /dev/null +++ b/src/bpsets/iam/IAMPolicyNoStatementsWithFullAccess.ts @@ -0,0 +1,67 @@ +import { + IAMClient, + ListPoliciesCommand, + GetPolicyVersionCommand, + DeletePolicyCommand +} from '@aws-sdk/client-iam' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class IAMPolicyNoStatementsWithAdminAccess implements BPSet { + private readonly client = new IAMClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getPolicies = async () => { + const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' })) + return response.Policies || [] + } + + private readonly getPolicyDefaultVersions = async (policyArn: string, versionId: string) => { + const response = await this.memoClient.send( + new GetPolicyVersionCommand({ PolicyArn: policyArn, VersionId: versionId }) + ) + return response.PolicyVersion! + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const policies = await this.getPolicies() + + for (const policy of policies) { + const policyVersion = await this.getPolicyDefaultVersions(policy.Arn!, policy.DefaultVersionId!) + + const policyDocument = JSON.parse(JSON.stringify(policyVersion.Document)) // Parse Document JSON string + const statements = Array.isArray(policyDocument.Statement) + ? policyDocument.Statement + : [policyDocument.Statement] + + for (const statement of statements) { + if ( + statement.Action === '*' && + statement.Resource === '*' && + statement.Effect === 'Allow' + ) { + nonCompliantResources.push(policy.Arn!) + break + } + } + + if (!nonCompliantResources.includes(policy.Arn!)) { + compliantResources.push(policy.Arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + await this.client.send(new DeletePolicyCommand({ PolicyArn: arn })) + } + } +} diff --git a/src/bpsets/iam/IAMRoleManagedPolicyCheck.ts b/src/bpsets/iam/IAMRoleManagedPolicyCheck.ts new file mode 100644 index 0000000..46d13d0 --- /dev/null +++ b/src/bpsets/iam/IAMRoleManagedPolicyCheck.ts @@ -0,0 +1,56 @@ +import { + IAMClient, + ListPoliciesCommand, + ListEntitiesForPolicyCommand +} from '@aws-sdk/client-iam' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class IAMRoleManagedPolicyCheck implements BPSet { + private readonly client = new IAMClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getPolicies = async () => { + const response = await this.memoClient.send(new ListPoliciesCommand({ Scope: 'Local' })) + return response.Policies || [] + } + + private readonly checkEntitiesForPolicy = async (policyArn: string) => { + const response = await this.memoClient.send( + new ListEntitiesForPolicyCommand({ PolicyArn: policyArn }) + ) + return { + attached: Boolean( + response.PolicyGroups?.length || response.PolicyUsers?.length || response.PolicyRoles?.length + ) + } + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const policies = await this.getPolicies() + + for (const policy of policies) { + const { attached } = await this.checkEntitiesForPolicy(policy.Arn!) + + if (attached) { + compliantResources.push(policy.Arn!) + } else { + nonCompliantResources.push(policy.Arn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + throw new Error( + 'Fixing orphaned managed policies requires manual review and removal. Ensure these policies are no longer needed.' + ) + } +} diff --git a/src/bpsets/rds/AuroraLastBackupRecoveryPointCreated.ts b/src/bpsets/rds/AuroraLastBackupRecoveryPointCreated.ts new file mode 100644 index 0000000..f617392 --- /dev/null +++ b/src/bpsets/rds/AuroraLastBackupRecoveryPointCreated.ts @@ -0,0 +1,80 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { + BackupClient, + ListRecoveryPointsByResourceCommand +} from '@aws-sdk/client-backup' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class AuroraLastBackupRecoveryPointCreated implements BPSet { + private readonly rdsClient = new RDSClient({}) + private readonly backupClient = new BackupClient({}) + private readonly memoRdsClient = Memorizer.memo(this.rdsClient) + + private readonly getDBClusters = async () => { + const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + private readonly getRecoveryPoints = async (resourceArn: string) => { + const response = await this.backupClient.send( + new ListRecoveryPointsByResourceCommand({ ResourceArn: resourceArn }) + ) + return response.RecoveryPoints || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + const recoveryPoints = await this.getRecoveryPoints(cluster.DBClusterArn!) + const recoveryDates = recoveryPoints.map(rp => new Date(rp.CreationDate!)) + recoveryDates.sort((a, b) => b.getTime() - a.getTime()) + + if ( + recoveryDates.length > 0 && + new Date().getTime() - recoveryDates[0].getTime() < 24 * 60 * 60 * 1000 + ) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'backup-retention-period', value: '7' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const retentionPeriod = requiredParametersForFix.find( + param => param.name === 'backup-retention-period' + )?.value + + if (!retentionPeriod) { + throw new Error("Required parameter 'backup-retention-period' is missing.") + } + + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + + await this.rdsClient.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + BackupRetentionPeriod: parseInt(retentionPeriod, 10) + }) + ) + } + } +} diff --git a/src/bpsets/rds/AuroraMySQLBacktrackingEnabled.ts b/src/bpsets/rds/AuroraMySQLBacktrackingEnabled.ts new file mode 100644 index 0000000..34bfbd4 --- /dev/null +++ b/src/bpsets/rds/AuroraMySQLBacktrackingEnabled.ts @@ -0,0 +1,52 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class AuroraMySQLBacktrackingEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if ( + cluster.Engine === 'aurora-mysql' && + (!cluster.EarliestBacktrackTime || cluster.EarliestBacktrackTime === null) + ) { + nonCompliantResources.push(cluster.DBClusterArn!) + } else { + compliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + await this.client.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + BacktrackWindow: 3600 // Set backtracking window to 1 hour + }) + ) + } + } +} diff --git a/src/bpsets/rds/DBInstanceBackupEnabled.ts b/src/bpsets/rds/DBInstanceBackupEnabled.ts new file mode 100644 index 0000000..55c1425 --- /dev/null +++ b/src/bpsets/rds/DBInstanceBackupEnabled.ts @@ -0,0 +1,60 @@ +import { + RDSClient, + DescribeDBInstancesCommand, + ModifyDBInstanceCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class DBInstanceBackupEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBInstances = async () => { + const response = await this.memoClient.send(new DescribeDBInstancesCommand({})) + return response.DBInstances || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbInstances = await this.getDBInstances() + + for (const instance of dbInstances) { + if (instance.BackupRetentionPeriod && instance.BackupRetentionPeriod > 0) { + compliantResources.push(instance.DBInstanceArn!) + } else { + nonCompliantResources.push(instance.DBInstanceArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'retention-period', value: '7' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const retentionPeriod = requiredParametersForFix.find( + param => param.name === 'retention-period' + )?.value + + if (!retentionPeriod) { + throw new Error("Required parameter 'retention-period' is missing.") + } + + for (const arn of nonCompliantResources) { + const instanceId = arn.split(':instance/')[1] + await this.client.send( + new ModifyDBInstanceCommand({ + DBInstanceIdentifier: instanceId, + BackupRetentionPeriod: parseInt(retentionPeriod, 10) + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSClusterAutoMinorVersionUpgradeEnabled.ts b/src/bpsets/rds/RDSClusterAutoMinorVersionUpgradeEnabled.ts new file mode 100644 index 0000000..bca7f2d --- /dev/null +++ b/src/bpsets/rds/RDSClusterAutoMinorVersionUpgradeEnabled.ts @@ -0,0 +1,50 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSClusterAutoMinorVersionUpgradeEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if (cluster.Engine === 'docdb' || cluster.AutoMinorVersionUpgrade) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + + await this.client.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + AutoMinorVersionUpgrade: true + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSClusterDefaultAdminCheck.ts b/src/bpsets/rds/RDSClusterDefaultAdminCheck.ts new file mode 100644 index 0000000..a63abd9 --- /dev/null +++ b/src/bpsets/rds/RDSClusterDefaultAdminCheck.ts @@ -0,0 +1,67 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSClusterDefaultAdminCheck implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if (!['admin', 'postgres'].includes(cluster.MasterUsername!)) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [ + { name: 'new-master-username', value: '' }, + { name: 'new-master-password', value: '' } + ] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const newMasterUsername = requiredParametersForFix.find( + param => param.name === 'new-master-username' + )?.value + const newMasterPassword = requiredParametersForFix.find( + param => param.name === 'new-master-password' + )?.value + + if (!newMasterUsername || !newMasterPassword) { + throw new Error("Required parameters 'new-master-username' and 'new-master-password' are missing.") + } + + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + + await this.client.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + MasterUserPassword: newMasterPassword + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSClusterDeletionProtectionEnabled.ts b/src/bpsets/rds/RDSClusterDeletionProtectionEnabled.ts new file mode 100644 index 0000000..6cfbe34 --- /dev/null +++ b/src/bpsets/rds/RDSClusterDeletionProtectionEnabled.ts @@ -0,0 +1,50 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSClusterDeletionProtectionEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if (cluster.DeletionProtection) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + + await this.client.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + DeletionProtection: true + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSClusterEncryptedAtRest.ts b/src/bpsets/rds/RDSClusterEncryptedAtRest.ts new file mode 100644 index 0000000..ac53ca8 --- /dev/null +++ b/src/bpsets/rds/RDSClusterEncryptedAtRest.ts @@ -0,0 +1,43 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSClusterEncryptedAtRest implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if (cluster.StorageEncrypted) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + throw new Error( + 'Fixing encryption at rest requires recreating the cluster. Please manually recreate the cluster with encryption enabled.' + ) + } +} diff --git a/src/bpsets/rds/RDSClusterIAMAuthenticationEnabled.ts b/src/bpsets/rds/RDSClusterIAMAuthenticationEnabled.ts new file mode 100644 index 0000000..2c01579 --- /dev/null +++ b/src/bpsets/rds/RDSClusterIAMAuthenticationEnabled.ts @@ -0,0 +1,53 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSClusterIAMAuthenticationEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if ( + cluster.Engine === 'docdb' || + cluster.IAMDatabaseAuthenticationEnabled + ) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + + await this.client.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + EnableIAMDatabaseAuthentication: true + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSClusterMultiAZEnabled.ts b/src/bpsets/rds/RDSClusterMultiAZEnabled.ts new file mode 100644 index 0000000..b56b6a2 --- /dev/null +++ b/src/bpsets/rds/RDSClusterMultiAZEnabled.ts @@ -0,0 +1,43 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSClusterMultiAZEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if ((cluster.AvailabilityZones || []).length > 1) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'additional-azs', value: '2' }] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + throw new Error( + 'Enabling Multi-AZ requires cluster reconfiguration. This must be performed manually.' + ) + } +} diff --git a/src/bpsets/rds/RDSDBSecurityGroupNotAllowed.ts b/src/bpsets/rds/RDSDBSecurityGroupNotAllowed.ts new file mode 100644 index 0000000..d5274b6 --- /dev/null +++ b/src/bpsets/rds/RDSDBSecurityGroupNotAllowed.ts @@ -0,0 +1,64 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { EC2Client, DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSDBSecurityGroupNotAllowed implements BPSet { + private readonly rdsClient = new RDSClient({}) + private readonly ec2Client = new EC2Client({}) + private readonly memoRdsClient = Memorizer.memo(this.rdsClient) + private readonly memoEc2Client = Memorizer.memo(this.ec2Client) + + private readonly getDBClusters = async () => { + const response = await this.memoRdsClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + private readonly getDefaultSecurityGroups = async () => { + const response = await this.memoEc2Client.send( + new DescribeSecurityGroupsCommand({ Filters: [{ Name: 'group-name', Values: ['default'] }] }) + ) + return response.SecurityGroups || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbClusters = await this.getDBClusters() + const defaultSecurityGroupIds = (await this.getDefaultSecurityGroups()).map(sg => sg.GroupId!) + + for (const cluster of dbClusters) { + const activeSecurityGroups = cluster.VpcSecurityGroups?.filter(sg => sg.Status === 'active') || [] + + if (activeSecurityGroups.some(sg => defaultSecurityGroupIds.includes(sg.VpcSecurityGroupId!))) { + nonCompliantResources.push(cluster.DBClusterArn!) + } else { + compliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + + // Remove default security groups by modifying the cluster's security group configuration + await this.rdsClient.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + VpcSecurityGroupIds: [] // Update to valid non-default security groups + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSEnhancedMonitoringEnabled.ts b/src/bpsets/rds/RDSEnhancedMonitoringEnabled.ts new file mode 100644 index 0000000..7c27a7b --- /dev/null +++ b/src/bpsets/rds/RDSEnhancedMonitoringEnabled.ts @@ -0,0 +1,61 @@ +import { + RDSClient, + DescribeDBInstancesCommand, + ModifyDBInstanceCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSEnhancedMonitoringEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBInstances = async () => { + const response = await this.memoClient.send(new DescribeDBInstancesCommand({})) + return response.DBInstances || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbInstances = await this.getDBInstances() + + for (const instance of dbInstances) { + if (instance.MonitoringInterval && instance.MonitoringInterval > 0) { + compliantResources.push(instance.DBInstanceArn!) + } else { + nonCompliantResources.push(instance.DBInstanceArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [{ name: 'monitoring-interval', value: '60' }] + } + } + + public readonly fix = async ( + nonCompliantResources: string[], + requiredParametersForFix: { name: string; value: string }[] + ) => { + const monitoringInterval = requiredParametersForFix.find( + param => param.name === 'monitoring-interval' + )?.value + + if (!monitoringInterval) { + throw new Error("Required parameter 'monitoring-interval' is missing.") + } + + for (const arn of nonCompliantResources) { + const instanceId = arn.split(':instance/')[1] + + await this.client.send( + new ModifyDBInstanceCommand({ + DBInstanceIdentifier: instanceId, + MonitoringInterval: parseInt(monitoringInterval, 10) + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSInstancePublicAccessCheck.ts b/src/bpsets/rds/RDSInstancePublicAccessCheck.ts new file mode 100644 index 0000000..d86c081 --- /dev/null +++ b/src/bpsets/rds/RDSInstancePublicAccessCheck.ts @@ -0,0 +1,50 @@ +import { + RDSClient, + DescribeDBInstancesCommand, + ModifyDBInstanceCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSInstancePublicAccessCheck implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBInstances = async () => { + const response = await this.memoClient.send(new DescribeDBInstancesCommand({})) + return response.DBInstances || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const dbInstances = await this.getDBInstances() + + for (const instance of dbInstances) { + if (instance.PubliclyAccessible) { + nonCompliantResources.push(instance.DBInstanceArn!) + } else { + compliantResources.push(instance.DBInstanceArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const instanceId = arn.split(':instance/')[1] + + await this.client.send( + new ModifyDBInstanceCommand({ + DBInstanceIdentifier: instanceId, + PubliclyAccessible: false + }) + ) + } + } +} diff --git a/src/bpsets/rds/RDSLoggingEnabled.ts b/src/bpsets/rds/RDSLoggingEnabled.ts new file mode 100644 index 0000000..d1867d6 --- /dev/null +++ b/src/bpsets/rds/RDSLoggingEnabled.ts @@ -0,0 +1,70 @@ +import { + RDSClient, + DescribeDBClustersCommand, + ModifyDBClusterCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSLoggingEnabled implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusters = async () => { + const response = await this.memoClient.send(new DescribeDBClustersCommand({})) + return response.DBClusters || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const logsForEngine = { + 'aurora-mysql': ['audit', 'error', 'general', 'slowquery'], + 'aurora-postgresql': ['postgresql'], + 'docdb': ['audit', 'profiler'] + } + const dbClusters = await this.getDBClusters() + + for (const cluster of dbClusters) { + if ( + JSON.stringify(cluster.EnabledCloudwatchLogsExports || []) === + JSON.stringify((logsForEngine as any)[cluster.Engine!] || []) + ) { + compliantResources.push(cluster.DBClusterArn!) + } else { + nonCompliantResources.push(cluster.DBClusterArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [] + } + } + + public readonly fix = async (nonCompliantResources: string[]) => { + for (const arn of nonCompliantResources) { + const clusterId = arn.split(':cluster/')[1] + const logsForEngine = { + 'aurora-mysql': ['audit', 'error', 'general', 'slowquery'], + 'aurora-postgresql': ['postgresql'], + 'docdb': ['audit', 'profiler'] + } + + const dbClusters = await this.getDBClusters() + const cluster = dbClusters.find(c => c.DBClusterArn === arn) + + if (cluster) { + const logsToEnable = (logsForEngine as any)[cluster.Engine!] + + await this.client.send( + new ModifyDBClusterCommand({ + DBClusterIdentifier: clusterId, + CloudwatchLogsExportConfiguration: { EnableLogTypes: logsToEnable } + }) + ) + } + } + } +} diff --git a/src/bpsets/rds/RDSSnapshotEncrypted.ts b/src/bpsets/rds/RDSSnapshotEncrypted.ts new file mode 100644 index 0000000..98fe85c --- /dev/null +++ b/src/bpsets/rds/RDSSnapshotEncrypted.ts @@ -0,0 +1,62 @@ +import { + RDSClient, + DescribeDBClusterSnapshotsCommand, + CopyDBClusterSnapshotCommand +} from '@aws-sdk/client-rds' +import { BPSet } from '../BPSet' +import { Memorizer } from '../../Memorizer' + +export class RDSSnapshotEncrypted implements BPSet { + private readonly client = new RDSClient({}) + private readonly memoClient = Memorizer.memo(this.client) + + private readonly getDBClusterSnapshots = async () => { + const response = await this.memoClient.send(new DescribeDBClusterSnapshotsCommand({})) + return response.DBClusterSnapshots || [] + } + + public readonly check = async () => { + const compliantResources = [] + const nonCompliantResources = [] + const snapshots = await this.getDBClusterSnapshots() + + for (const snapshot of snapshots) { + if (snapshot.StorageEncrypted) { + compliantResources.push(snapshot.DBClusterSnapshotArn!) + } else { + nonCompliantResources.push(snapshot.DBClusterSnapshotArn!) + } + } + + return { + compliantResources, + nonCompliantResources, + requiredParametersForFix: [ + { name: 'kms-key-id', value: '' } // Replace with your 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) { + const snapshotId = arn.split(':snapshot:')[1] + + await this.client.send( + new CopyDBClusterSnapshotCommand({ + SourceDBClusterSnapshotIdentifier: arn, + TargetDBClusterSnapshotIdentifier: `${snapshotId}-encrypted`, + KmsKeyId: kmsKeyId + }) + ) + } + } +}