diff --git a/NOTICE.txt b/NOTICE.txt index 5bad47f4..433f02db 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,2 +1,2 @@ rdk -Copyright 2017-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Copyright 2017-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/README.md b/README.md index 8cc1c093..f7d851c5 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ For complete documentation, including command reference, check out the ## Getting Started -Uses python 3.7/3.8/3.9 and is installed via pip. Requires you to have +Uses Python 3.7+ and is installed via pip. Requires you to have an AWS account and sufficient permissions to manage the Config service, and to create S3 Buckets, Roles, and Lambda Functions. An AWS IAM Policy Document that describes the minimum necessary permissions can be found @@ -126,7 +126,7 @@ rule and populate it with several files, including a skeleton of your Lambda code. ```bash -rdk create MyRule --runtime python3.8 --resource-types AWS::EC2::Instance --input-parameters '{"desiredInstanceType":"t2.micro"}' +rdk create MyRule --runtime python3.10 --resource-types AWS::EC2::Instance --input-parameters '{"desiredInstanceType":"t2.micro"}' Running create! Local Rule files created. ``` @@ -220,7 +220,7 @@ will overwrite existing values, any that you do not specify will not be changed. ```bash -rdk modify MyRule --runtime python3.9 --maximum-frequency TwentyFour_Hours --input-parameters '{"desiredInstanceType":"t2.micro"}' +rdk modify MyRule --runtime python3.10 --maximum-frequency TwentyFour_Hours --input-parameters '{"desiredInstanceType":"t2.micro"}' Running modify! Modified Rule 'MyRule'. Use the `deploy` command to push your changes to AWS. ``` @@ -239,7 +239,7 @@ Once you have completed your compliance validation code and set your Rule's configuration, you can deploy the Rule to your account using the `deploy` command. This will zip up your code (and the other associated code files, if any) into a deployable package (or run a gradle build if -you have selected the java8 runtime or run the lambda packaging step +you have selected the java8 runtime or run the Lambda packaging step from the dotnet CLI if you have selected the dotnetcore1.0 runtime), copy that zip file to S3, and then launch or update a CloudFormation stack that defines your Config Rule, Lambda function, and the necessary @@ -272,8 +272,8 @@ You can also deploy the Rule to your AWS Organization using the `deploy-organization` command. For successful evaluation of custom rules in child accounts, please make sure you do one of the following: -1. Set ASSUME_ROLE_MODE in Lambda code to True, to get the lambda to assume the Role attached on the Config Service and confirm that the role trusts the master account where the Lambda function is going to be deployed. -2. Set ASSUME_ROLE_MODE in Lambda code to True, to get the lambda to assume a custom role and define an optional parameter with key as ExecutionRoleName and set the value to your custom role name; confirm that the role trusts the master account of the organization where the Lambda function will be deployed. +1. Set ASSUME_ROLE_MODE in Lambda code to True, to get the Lambda to assume the Role attached on the Config Service and confirm that the role trusts the master account where the Lambda function is going to be deployed. +2. Set ASSUME_ROLE_MODE in Lambda code to True, to get the Lambda to assume a custom role and define an optional parameter with key as ExecutionRoleName and set the value to your custom role name; confirm that the role trusts the master account of the organization where the Lambda function will be deployed. ```bash rdk deploy-organization MyRule @@ -300,7 +300,7 @@ doesn't exist within 7 hours of adding an account to your organization. ### View Logs For Deployed Rule Once the Rule has been deployed to AWS you can get the CloudWatch logs -associated with your lambda function using the `logs` command. +associated with your Lambda function using the `logs` command. ```bash rdk logs MyRule -n 5 @@ -321,8 +321,8 @@ make sure it is behaving as expected. The `testing` directory contains scripts and buildspec files that I use to run basic functionality tests across a variety of CLI environments -(currently Ubuntu linux running python 3.7/3.8/3.9, and Windows Server -running python3.9). If there is interest I can release a CloudFormation +(currently Ubuntu Linux running Python 3.7/3.8/3.9/3.10, and Windows Server +running Python 3.10). If there is interest I can release a CloudFormation template that could be used to build the test environment, let me know if this is something you want! @@ -340,10 +340,10 @@ are used by other teams or departments. This gives the compliance team confidence that their rule logic cannot be tampered with and makes it much easier for them to modify rule logic without having to go through a complex deployment process to potentially hundreds of AWS accounts. The -cross-account pattern uses two advanced RDK features +cross-account pattern uses two advanced RDK features: -- Functions-only deployment -- create-rule-template command +- `--functions-only` (`-f`) deployment +- `create-rule-template` command #### Functions-Only Deployment @@ -369,8 +369,8 @@ This command generates a CloudFormation template that defines the AWS Config rules themselves, along with the Config Role, Config data bucket, Configuration Recorder, and Delivery channel necessary for the Config rules to work in a satellite account. You must specify the file name for -the generated template using the [--output-file]{.title-ref} or -[o]{.title-ref} command line flags. The generated template takes a +the generated template using the `--output-file` or +`-o` command line flags. The generated template takes a single parameter of the AccountID of the central compliance account that contains the Lambda functions that will back your custom Config Rules. The generated template can be deployed in the desired satellite accounts @@ -394,7 +394,7 @@ by rdk. To disable the supported resource check use the optional flag '--skip-supported-resource-check' during the create command. ```bash -rdk create MyRule --runtime python3.8 --resource-types AWS::New::ResourceType --skip-supported-resource-check +rdk create MyRule --runtime python3.10 --resource-types AWS::New::ResourceType --skip-supported-resource-check 'AWS::New::ResourceType' not found in list of accepted resource types. Skip-Supported-Resource-Check Flag set (--skip-supported-resource-check), ignoring missing resource type error. Running create! @@ -413,7 +413,7 @@ performing `rdk create`. This opens up new features like : 2. Custom lambda function naming as per personal or enterprise standards. ```bash -rdk create MyLongerRuleName --runtime python3.8 --resource-types AWS::EC2::Instance --custom-lambda-name custom-prefix-for-MyLongerRuleName +rdk create MyLongerRuleName --runtime python3.10 --resource-types AWS::EC2::Instance --custom-lambda-name custom-prefix-for-MyLongerRuleName Running create! Local Rule files created. ``` @@ -533,21 +533,21 @@ are happy to help and discuss. ## Contacts -- **Benjamin Morris** - [bmorrissirromb](https://github.com/bmorrissirromb) - *current maintainer* -- **Julio Delgado Jr** - [tekdj7](https://github.com/tekdj7) - *current maintainer* +- **Benjamin Morris** - [bmorrissirromb](https://github.com/bmorrissirromb) - _current maintainer_ +- **Julio Delgado Jr** - [tekdj7](https://github.com/tekdj7) - _current maintainer_ ## Past Contributors -- **Michael Borchert** - *Original Python version* -- **Jonathan Rault** - *Original Design, testing, feedback* -- **Greg Kim and Chris Gutierrez** - *Initial work and CI definitions* -- **Henry Huang** - *Original CFN templates and other code* -- **Santosh Kumar** - *maintainer* -- **Jose Obando** - *maintainer* -- **Jarrett Andrulis** - [jarrettandrulis](https://github.com/jarrettandrulis) - *maintainer* -- **Sandeep Batchu** - [batchus](https://github.com/batchus) - *maintainer* -- **Mark Beacom** - [mbeacom](https://github.com/mbeacom) - *maintainer* -- **Ricky Chau** - [rickychau2780](https://github.com/rickychau2780) - *maintainer* +- **Michael Borchert** - _Original Python version_ +- **Jonathan Rault** - _Original Design, testing, feedback_ +- **Greg Kim and Chris Gutierrez** - _Initial work and CI definitions_ +- **Henry Huang** - _Original CFN templates and other code_ +- **Santosh Kumar** - _maintainer_ +- **Jose Obando** - _maintainer_ +- **Jarrett Andrulis** - [jarrettandrulis](https://github.com/jarrettandrulis) - _maintainer_ +- **Sandeep Batchu** - [batchus](https://github.com/batchus) - _maintainer_ +- **Mark Beacom** - [mbeacom](https://github.com/mbeacom) - _maintainer_ +- **Ricky Chau** - [rickychau2780](https://github.com/rickychau2780) - _maintainer_ ## License diff --git a/developer_notes.md b/developer_notes.md new file mode 100644 index 00000000..4702f5e6 --- /dev/null +++ b/developer_notes.md @@ -0,0 +1,34 @@ +# Developer Notes + +These notes are intended to help RDK developers update the repository consistently. + +## New Runtime Support Process + +These instructions document the parts of the repository that need to be updated when support for a new Lambda runtime is added. + +### Update pyproject.toml + +- Add to `classifiers` list: + +```yaml +"Programming Language :: Python :: ," +``` + +- Add to `include` list: + +```yaml +"rdk/template/runtime/python/*", +"rdk/template/runtime/python-lib/*", +``` + +### Update README.md + +- Update documentation and examples + +### Update rdk.py + +- Update references to include new version + +### Update Linux and Windows Buildspec files (`testing` folder) + +- Add new test cases for the new version diff --git a/pyproject.toml b/pyproject.toml index 2f7948b0..6a15747f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. [tool.poetry] name = "rdk" -version = "0.14.0" +version = "0.15.0" description = "Rule Development Kit CLI for AWS Config" authors = [ "AWS RDK Maintainers ", @@ -25,6 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ] include = [ "README.md", @@ -46,6 +47,8 @@ include = [ "rdk/template/runtime/python3.8-lib/*", "rdk/template/runtime/python3.9/*", "rdk/template/runtime/python3.9-lib/*", + "rdk/template/runtime/python3.10/*", + "rdk/template/runtime/python3.10-lib/*", "rdk/template/runtime/dotnetcore1.0/*", "rdk/template/runtime/dotnetcore1.0/bin/*", "rdk/template/runtime/dotnetcore1.0/obj/*", diff --git a/rdk-workshop/instructions.md b/rdk-workshop/instructions.md index 0bbafc2a..210f13c5 100644 --- a/rdk-workshop/instructions.md +++ b/rdk-workshop/instructions.md @@ -113,7 +113,7 @@ Note: It might take up to 2 hours to get the information about the CIS benchmark ## (Optional) Going further 7. Discover all the available [Managed Config Rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html). -8. Navigate to [AWS System Manager Automation Documents](https://eu-west-1.console.aws.amazon.com/systems-manager/documents?region=eu-west-1) to discover all existing remediation actions. +8. Navigate to [AWS System Manager Automation Documents](https://us-east-1.console.aws.amazon.com/systems-manager/documents?region=us-east-1) to discover all existing remediation actions. # Lab 2: Writing Your First Config Rule diff --git a/rdk/__init__.py b/rdk/__init__.py index a5407534..d03245c4 100644 --- a/rdk/__init__.py +++ b/rdk/__init__.py @@ -6,4 +6,4 @@ # # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -MY_VERSION = "0.14.0" +MY_VERSION = "0.15.0" diff --git a/rdk/rdk.py b/rdk/rdk.py index a8274675..4cd52bbc 100644 --- a/rdk/rdk.py +++ b/rdk/rdk.py @@ -1,10 +1,16 @@ # Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. # -# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at +# Licensed under the Apache License, Version 2.0 (the "License"). +# +# You may not use this file except in compliance with the License. A copy of the License is located at # # http://aws.amazon.com/apache2.0/ # -# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +# or in the "license" file accompanying this file. +# +# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +# See the License for the specific language governing permissions and limitations under the License. import argparse import base64 import fileinput @@ -69,15 +75,20 @@ "sa-east-1": "5", } -RDKLIB_LAYER_SAR_ID = "arn:aws:serverlessrepo:ap-southeast-1:711761543063:applications/rdklib" +RDKLIB_LAYER_SAR_ID = ( + "arn:aws:serverlessrepo:ap-southeast-1:711761543063:applications/rdklib" +) RDKLIB_ARN_STRING = "arn:aws:lambda:{region}:711761543063:layer:rdklib-layer:{version}" -PARALLEL_COMMAND_THROTTLE_PERIOD = 2 # 2 seconds, used in running commands in parallel over multiple regions +PARALLEL_COMMAND_THROTTLE_PERIOD = ( + 2 # 2 seconds, used in running commands in parallel over multiple regions +) -# this need to be update whenever config service supports more resource types : https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html +# This need to be update whenever config service supports more resource types +# See: https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html accepted_resource_types = [ - "AWS::AccessAnalyzer::Analyzer", "AWS::ACM::Certificate", + "AWS::AccessAnalyzer::Analyzer", "AWS::AmazonMQ::Broker", "AWS::ApiGateway::RestApi", "AWS::ApiGateway::Stage", @@ -85,11 +96,14 @@ "AWS::ApiGatewayV2::Stage", "AWS::AppConfig::Application", "AWS::AppConfig::ConfigurationProfile", + "AWS::AppConfig::DeploymentStrategy", "AWS::AppConfig::Environment", + "AWS::AppFlow::Flow", "AWS::AppStream::DirectoryConfig", "AWS::AppSync::GraphQLApi", - "AWS::Athena::WorkGroup", "AWS::Athena::DataCatalog", + "AWS::Athena::WorkGroup", + "AWS::AuditManager::Assessment", "AWS::AutoScaling::AutoScalingGroup", "AWS::AutoScaling::LaunchConfiguration", "AWS::AutoScaling::ScalingPolicy", @@ -109,6 +123,7 @@ "AWS::CloudFront::StreamingDistribution", "AWS::CloudTrail::Trail", "AWS::CloudWatch::Alarm", + "AWS::CloudWatch::MetricStream", "AWS::CodeBuild::Project", "AWS::CodeDeploy::Application", "AWS::CodeDeploy::DeploymentConfig", @@ -120,6 +135,11 @@ "AWS::Config::ResourceCompliance", "AWS::Connect::PhoneNumber", "AWS::CustomerProfiles::Domain", + "AWS::DMS::Certificate", + "AWS::DMS::EventSubscription", + "AWS::DMS::ReplicationInstance", + "AWS::DMS::ReplicationSubnetGroup", + "AWS::DMS::ReplicationTask", "AWS::DataSync::LocationEFS", "AWS::DataSync::LocationFSxLustre", "AWS::DataSync::LocationFSxWindows", @@ -130,22 +150,20 @@ "AWS::DataSync::LocationSMB", "AWS::DataSync::Task", "AWS::Detective::Graph", + "AWS::DeviceFarm::InstanceProfile", + "AWS::DeviceFarm::Project", "AWS::DeviceFarm::TestGridProject", - "AWS::DMS::Certificate", - "AWS::DMS::EventSubscription", - "AWS::DMS::ReplicationInstance", - "AWS::DMS::ReplicationSubnetGroup", - "AWS::DMS::ReplicationTask", "AWS::DynamoDB::Table", "AWS::EC2::CustomerGateway", "AWS::EC2::DHCPOptions", + "AWS::EC2::EC2Fleet", "AWS::EC2::EIP", "AWS::EC2::EgressOnlyInternetGateway", "AWS::EC2::FlowLog", "AWS::EC2::Host", + "AWS::EC2::IPAM", "AWS::EC2::Instance", "AWS::EC2::InternetGateway", - "AWS::EC2::IPAM", "AWS::EC2::LaunchTemplate", "AWS::EC2::NatGateway", "AWS::EC2::NetworkAcl", @@ -156,6 +174,7 @@ "AWS::EC2::RouteTable", "AWS::EC2::SecurityGroup", "AWS::EC2::Subnet", + "AWS::EC2::SubnetRouteTableAssociation", "AWS::EC2::TrafficMirrorFilter", "AWS::EC2::TrafficMirrorSession", "AWS::EC2::TrafficMirrorTarget", @@ -170,8 +189,9 @@ "AWS::EC2::VPNGateway", "AWS::EC2::Volume", "AWS::ECR::PublicRepository", - "AWS::ECR::Repository", + "AWS::ECR::PullThroughCacheRule", "AWS::ECR::RegistryPolicy", + "AWS::ECR::Repository", "AWS::ECS::Cluster", "AWS::ECS::Service", "AWS::ECS::TaskDefinition", @@ -189,16 +209,16 @@ "AWS::ElasticLoadBalancingV2::Listener", "AWS::ElasticLoadBalancingV2::LoadBalancer", "AWS::ElasticSearch::Domain", - "AWS::Events::Archive", + "AWS::EventSchemas::Discoverer", + "AWS::EventSchemas::Registry", + "AWS::EventSchemas::RegistryPolicy", + "AWS::EventSchemas::Schema", "AWS::Events::ApiDestination", + "AWS::Events::Archive", "AWS::Events::Connection", "AWS::Events::Endpoint", "AWS::Events::EventBus", "AWS::Events::Rule", - "AWS::EventSchemas::Discoverer", - "AWS::EventSchemas::Registry", - "AWS::EventSchemas::RegistryPolicy", - "AWS::EventSchemas::Schema", "AWS::FIS::ExperimentTemplate", "AWS::FraudDetector::EntityType", "AWS::FraudDetector::Label", @@ -210,6 +230,7 @@ "AWS::Glue::Classifier", "AWS::Glue::Job", "AWS::Glue::MLTransform", + "AWS::GroundStation::Config", "AWS::GuardDuty::Detector", "AWS::GuardDuty::Filter", "AWS::GuardDuty::IPSet", @@ -219,13 +240,18 @@ "AWS::IAM::Policy", "AWS::IAM::Role", "AWS::IAM::User", + "AWS::IVS::Channel", + "AWS::IVS::PlaybackKeyPair", + "AWS::IVS::RecordingConfiguration", "AWS::ImageBuilder::ContainerRecipe", "AWS::ImageBuilder::DistributionConfiguration", + "AWS::ImageBuilder::ImagePipeline", "AWS::ImageBuilder::InfrastructureConfiguration", "AWS::IoT::AccountAuditConfiguration", "AWS::IoT::Authorizer", "AWS::IoT::CustomMetric", "AWS::IoT::Dimension", + "AWS::IoT::FleetMetric", "AWS::IoT::MitigationAction", "AWS::IoT::Policy", "AWS::IoT::RoleAlias", @@ -246,9 +272,7 @@ "AWS::IoTTwinMaker::Entity", "AWS::IoTTwinMaker::Scene", "AWS::IoTTwinMaker::Workspace", - "AWS::IVS::Channel", - "AWS::IVS::PlaybackKeyPair", - "AWS::IVS::RecordingConfiguration", + "AWS::IoTWireless::ServiceProfile", "AWS::KMS::Alias", "AWS::KMS::Key", "AWS::Kinesis::Stream", @@ -264,14 +288,21 @@ "AWS::Lightsail::StaticIp", "AWS::LookoutMetrics::Alert", "AWS::LookoutVision::Project", + "AWS::MSK::Cluster", "AWS::MediaPackage::PackagingConfiguration", "AWS::MediaPackage::PackagingGroup", - "AWS::MSK::Cluster", "AWS::NetworkFirewall::Firewall", "AWS::NetworkFirewall::FirewallPolicy", "AWS::NetworkFirewall::RuleGroup", + "AWS::NetworkFirewall::TLSInspectionConfiguration", + "AWS::NetworkManager::Device", + "AWS::NetworkManager::GlobalNetwork", + "AWS::NetworkManager::Link", + "AWS::NetworkManager::Site", "AWS::NetworkManager::TransitGatewayRegistration", "AWS::OpenSearch::Domain", + "AWS::Panorama::Package", + "AWS::Pinpoint::App", "AWS::Pinpoint::ApplicationSettings", "AWS::Pinpoint::Segment", "AWS::QLDB::Ledger", @@ -283,12 +314,14 @@ "AWS::RDS::DBSubnetGroup", "AWS::RDS::EventSubscription", "AWS::RDS::GlobalCluster", + "AWS::RUM::AppMonitor", "AWS::Redshift::Cluster", "AWS::Redshift::ClusterParameterGroup", "AWS::Redshift::ClusterSecurityGroup", "AWS::Redshift::ClusterSnapshot", "AWS::Redshift::ClusterSubnetGroup", "AWS::Redshift::EventSubscription", + "AWS::Redshift::ScheduledAction", "AWS::ResilienceHub::ResiliencyPolicy", "AWS::RoboMaker::RobotApplication", "AWS::RoboMaker::RobotApplicationVersion", @@ -299,27 +332,34 @@ "AWS::Route53RecoveryControl::ControlPanel", "AWS::Route53RecoveryControl::RoutingControl", "AWS::Route53RecoveryControl::SafetyRule", - "AWS::Route53RecoveryReadiness::ResourceSet", "AWS::Route53RecoveryReadiness::Cell", "AWS::Route53RecoveryReadiness::ReadinessCheck", "AWS::Route53RecoveryReadiness::RecoveryGroup", + "AWS::Route53RecoveryReadiness::ResourceSet", "AWS::Route53Resolver::FirewallDomainList", + "AWS::Route53Resolver::FirewallRuleGroupAssociation", "AWS::Route53Resolver::ResolverEndpoint", "AWS::Route53Resolver::ResolverRule", "AWS::Route53Resolver::ResolverRuleAssociation", - "AWS::RUM::AppMonitor", "AWS::S3::AccountPublicAccessBlock", "AWS::S3::Bucket", "AWS::S3::MultiRegionAccessPoint", "AWS::S3::StorageLens", + "AWS::SES::ConfigurationSet", + "AWS::SES::ContactList", + "AWS::SES::ReceiptFilter", + "AWS::SES::ReceiptRuleSet", + "AWS::SES::Template", "AWS::SNS::Topic", "AWS::SQS::Queue", "AWS::SSM::AssociationCompliance", "AWS::SSM::FileData", "AWS::SSM::ManagedInstanceInventory", "AWS::SSM::PatchCompliance", + "AWS::SageMaker::AppImageConfig", "AWS::SageMaker::CodeRepository", "AWS::SageMaker::EndpointConfig", + "AWS::SageMaker::Image", "AWS::SageMaker::Model", "AWS::SageMaker::NotebookInstance", "AWS::SageMaker::NotebookInstanceLifecycleConfig", @@ -331,11 +371,6 @@ "AWS::ServiceDiscovery::HttpNamespace", "AWS::ServiceDiscovery::PublicDnsNamespace", "AWS::ServiceDiscovery::Service", - "AWS::SES::ConfigurationSet", - "AWS::SES::ContactList", - "AWS::SES::ReceiptFilter", - "AWS::SES::ReceiptRuleSet", - "AWS::SES::Template", "AWS::Shield::Protection", "AWS::ShieldRegional::Protection", "AWS::StepFunctions::Activity", @@ -372,7 +407,9 @@ { "Sid": "REMOTE", "Effect": "Allow", - "Principal": {"AWS": {"Fn::Sub": "arn:${AWS::Partition}:iam::${LambdaAccountId}:root"}}, + "Principal": { + "AWS": {"Fn::Sub": "arn:${AWS::Partition}:iam::${LambdaAccountId}:root"} + }, "Action": "sts:AssumeRole", }, ], @@ -383,7 +420,9 @@ { "Effect": "Allow", "Action": "s3:PutObject*", - "Resource": {"Fn::Sub": "arn:${AWS::Partition}:s3:::${ConfigBucket}/AWSLogs/${AWS::AccountId}/*"}, + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:s3:::${ConfigBucket}/AWSLogs/${AWS::AccountId}/*" + }, "Condition": {"StringLike": {"s3:x-amz-acl": "bucket-owner-full-control"}}, }, { @@ -404,10 +443,18 @@ def get_command_parser(): # formatter_class=argparse.RawDescriptionHelpFormatter, description="The RDK is a command-line utility for authoring, deploying, and testing custom AWS Config rules." ) - parser.add_argument("-p", "--profile", help="[optional] indicate which Profile to use.") - parser.add_argument("-k", "--access-key-id", help="[optional] Access Key ID to use.") - parser.add_argument("-s", "--secret-access-key", help="[optional] Secret Access Key to use.") - parser.add_argument("-r", "--region", help="Select the region to run the command in.") + parser.add_argument( + "-p", "--profile", help="[optional] indicate which Profile to use." + ) + parser.add_argument( + "-k", "--access-key-id", help="[optional] Access Key ID to use." + ) + parser.add_argument( + "-s", "--secret-access-key", help="[optional] Secret Access Key to use." + ) + parser.add_argument( + "-r", "--region", help="Select the region to run the command in." + ) parser.add_argument( "-f", "--region-file", @@ -442,7 +489,7 @@ def get_command_parser(): parser.add_argument( "command", metavar="", - help=f"Command to run. Refer to the usage instructions for each command for more details. Commands are: {rdk_commands})", + help=f"Command to run. Refer to the usage instructions for each command for more details. Commands are: {rdk_commands}", choices=rdk_commands, ) parser.add_argument( @@ -452,7 +499,11 @@ def get_command_parser(): help="Run `rdk --help` to see command-specific arguments.", ) parser.add_argument( - "-v", "--version", help="Display the version of this tool", action="version", version="%(prog)s " + MY_VERSION + "-v", + "--version", + help="Display the version of this tool", + action="version", + version="%(prog)s " + MY_VERSION, ) return parser @@ -534,7 +585,9 @@ def get_rule_parser(is_required, command): + command + " the Rule and metadata.", ) - parser.add_argument("rulename", metavar="", help="Rule name to create/modify") + parser.add_argument( + "rulename", metavar="", help="Rule name to create/modify" + ) runtime_group = parser.add_mutually_exclusive_group() runtime_group.add_argument( "-R", @@ -542,7 +595,6 @@ def get_rule_parser(is_required, command): required=False, help="Runtime for lambda function", choices=[ - "nodejs6.10", "java8", "python3.7", "python3.7-lib", @@ -550,16 +602,23 @@ def get_rule_parser(is_required, command): "python3.8-lib", "python3.9", "python3.9-lib", - "dotnetcore1.0", - "dotnetcore2.0", + "python3.10", + "python3.10-lib", ], metavar="", ) runtime_group.add_argument( - "--source-identifier", required=False, help="[optional] Used only for creating Managed Rules." + "--source-identifier", + required=False, + help="[optional] Used only for creating Managed Rules.", ) - parser.add_argument("-l", "--custom-lambda-name", required=False, help="[optional] Provide custom lambda name") - parser.set_defaults(runtime="python3.9-lib") + parser.add_argument( + "-l", + "--custom-lambda-name", + required=False, + help="[optional] Provide custom lambda name", + ) + parser.set_defaults(runtime="python3.10-lib") parser.add_argument( "-r", "--resource-types", @@ -571,17 +630,41 @@ def get_rule_parser(is_required, command): "--maximum-frequency", required=False, help="[optional] Maximum execution frequency for scheduled Rules", - choices=["One_Hour", "Three_Hours", "Six_Hours", "Twelve_Hours", "TwentyFour_Hours"], + choices=[ + "One_Hour", + "Three_Hours", + "Six_Hours", + "Twelve_Hours", + "TwentyFour_Hours", + ], ) - parser.add_argument("-i", "--input-parameters", help="[optional] JSON for required Config parameters.") - parser.add_argument("--optional-parameters", help="[optional] JSON for optional Config parameters.") - parser.add_argument("--tags", help="[optional] JSON for tags to be applied to all CFN created resources.") parser.add_argument( - "-s", "--rulesets", required=False, help="[optional] comma-delimited list of RuleSet names to add this Rule to." + "-i", + "--input-parameters", + help="[optional] JSON for required Config parameters.", ) - parser.add_argument("--remediation-action", required=False, help="[optional] SSM document for remediation.") parser.add_argument( - "--remediation-action-version", required=False, help="[optional] SSM document version for remediation action." + "--optional-parameters", help="[optional] JSON for optional Config parameters." + ) + parser.add_argument( + "--tags", + help="[optional] JSON for tags to be applied to all CFN created resources.", + ) + parser.add_argument( + "-s", + "--rulesets", + required=False, + help="[optional] comma-delimited list of RuleSet names to add this Rule to.", + ) + parser.add_argument( + "--remediation-action", + required=False, + help="[optional] SSM document for remediation.", + ) + parser.add_argument( + "--remediation-action-version", + required=False, + help="[optional] SSM document version for remediation action.", ) parser.add_argument( "--auto-remediate", @@ -595,7 +678,9 @@ def get_rule_parser(is_required, command): help="[optional] Number of times to retry automated remediation.", ) parser.add_argument( - "--auto-remediation-retry-time", required=False, help="[optional] Duration of automated remediation retries." + "--auto-remediation-retry-time", + required=False, + help="[optional] Duration of automated remediation retries.", ) parser.add_argument( "--remediation-concurrent-execution-percent", @@ -646,13 +731,27 @@ def get_deployment_parser(ForceArgument=False, Command="deploy"): parser = argparse.ArgumentParser( prog="rdk " + Command, - description="Used to " + Command + " the Config Rule " + direction + " the target account.", + description="Used to " + + Command + + " the Config Rule " + + direction + + " the target account.", + ) + parser.add_argument( + "rulename", + metavar="", + nargs="*", + help="Rule name(s) to deploy. Rule(s) will be pushed to AWS.", ) parser.add_argument( - "rulename", metavar="", nargs="*", help="Rule name(s) to deploy. Rule(s) will be pushed to AWS." + "--all", + "-a", + action="store_true", + help="All rules in the working directory will be deployed.", + ) + parser.add_argument( + "-s", "--rulesets", required=False, help="comma-delimited list of RuleSet names" ) - parser.add_argument("--all", "-a", action="store_true", help="All rules in the working directory will be deployed.") - parser.add_argument("-s", "--rulesets", required=False, help="comma-delimited list of RuleSet names") parser.add_argument( "-f", "--functions-only", @@ -736,20 +835,36 @@ def get_deployment_parser(ForceArgument=False, Command="deploy"): return parser -def get_deployment_organization_parser(ForceArgument=False, Command="deploy-organization"): +def get_deployment_organization_parser( + ForceArgument=False, Command="deploy-organization" +): direction = "to" if Command == "undeploy": direction = "from" parser = argparse.ArgumentParser( prog="rdk " + Command, - description="Used to " + Command + " the Config Rule " + direction + " the target Organization.", + description="Used to " + + Command + + " the Config Rule " + + direction + + " the target Organization.", + ) + parser.add_argument( + "rulename", + metavar="", + nargs="*", + help="Rule name(s) to deploy. Rule(s) will be pushed to AWS.", ) parser.add_argument( - "rulename", metavar="", nargs="*", help="Rule name(s) to deploy. Rule(s) will be pushed to AWS." + "--all", + "-a", + action="store_true", + help="All rules in the working directory will be deployed.", + ) + parser.add_argument( + "-s", "--rulesets", required=False, help="comma-delimited list of RuleSet names" ) - parser.add_argument("--all", "-a", action="store_true", help="All rules in the working directory will be deployed.") - parser.add_argument("-s", "--rulesets", required=False, help="comma-delimited list of RuleSet names") parser.add_argument( "-f", "--functions-only", @@ -834,13 +949,25 @@ def get_deployment_organization_parser(ForceArgument=False, Command="deploy-orga def get_export_parser(ForceArgument=False, Command="export"): - parser = argparse.ArgumentParser( - prog="rdk " + Command, description="Used to " + Command + " the Config Rule to terraform file." + prog="rdk " + Command, + description="Used to " + Command + " the Config Rule to terraform file.", + ) + parser.add_argument( + "rulename", + metavar="", + nargs="*", + help="Rule name(s) to export to a file.", + ) + parser.add_argument( + "-s", "--rulesets", required=False, help="comma-delimited list of RuleSet names" + ) + parser.add_argument( + "--all", + "-a", + action="store_true", + help="All rules in the working directory will be deployed.", ) - parser.add_argument("rulename", metavar="", nargs="*", help="Rule name(s) to export to a file.") - parser.add_argument("-s", "--rulesets", required=False, help="comma-delimited list of RuleSet names") - parser.add_argument("--all", "-a", action="store_true", help="All rules in the working directory will be deployed.") parser.add_argument( "--lambda-layers", required=False, @@ -878,8 +1005,16 @@ def get_export_parser(ForceArgument=False, Command="export"): required=False, help="[optional] Lambda Layer ARN that contains the desired rdklib. Note that Lambda Layers are region-specific.", ) - parser.add_argument("-v", "--version", required=True, help="Terraform version", choices=["0.11", "0.12"]) - parser.add_argument("-f", "--format", required=True, help="Export Format", choices=["terraform"]) + parser.add_argument( + "-v", + "--version", + required=True, + help="Terraform version", + choices=["0.11", "0.12"], + ) + parser.add_argument( + "-f", "--format", required=True, help="Export Format", choices=["terraform"] + ) parser.add_argument( "-g", "--generated-lambda-layer", @@ -897,15 +1032,36 @@ def get_export_parser(ForceArgument=False, Command="export"): def get_test_parser(command): - parser = argparse.ArgumentParser(prog="rdk " + command, description="Used to run tests on your Config Rule code.") - parser.add_argument("rulename", metavar="[,,...]", nargs="*", help="Rule name(s) to test") + parser = argparse.ArgumentParser( + prog="rdk " + command, description="Used to run tests on your Config Rule code." + ) parser.add_argument( - "--all", "-a", action="store_true", help="Test will be run against all rules in the working directory." + "rulename", + metavar="[,,...]", + nargs="*", + help="Rule name(s) to test", + ) + parser.add_argument( + "--all", + "-a", + action="store_true", + help="Test will be run against all rules in the working directory.", + ) + parser.add_argument( + "--test-ci-json", "-j", help="[optional] JSON for test CI for testing." + ) + parser.add_argument( + "--test-ci-types", "-t", help="[optional] CI type to use for testing." + ) + parser.add_argument( + "--verbose", "-v", action="store_true", help="[optional] Enable full log output" + ) + parser.add_argument( + "-s", + "--rulesets", + required=False, + help="[optional] comma-delimited list of RuleSet names", ) - parser.add_argument("--test-ci-json", "-j", help="[optional] JSON for test CI for testing.") - parser.add_argument("--test-ci-types", "-t", help="[optional] CI type to use for testing.") - parser.add_argument("--verbose", "-v", action="store_true", help="[optional] Enable full log output") - parser.add_argument("-s", "--rulesets", required=False, help="[optional] comma-delimited list of RuleSet names") return parser @@ -933,11 +1089,21 @@ def get_logs_parser(): usage="rdk logs [-n/--number NUMBER] [-f/--follow]", description="Displays CloudWatch logs for the Lambda Function for the specified Rule.", ) - parser.add_argument("rulename", metavar="", help="Rule whose logs will be displayed") parser.add_argument( - "-f", "--follow", action="store_true", help="[optional] Continuously poll Lambda logs and write to stdout." + "rulename", metavar="", help="Rule whose logs will be displayed" + ) + parser.add_argument( + "-f", + "--follow", + action="store_true", + help="[optional] Continuously poll Lambda logs and write to stdout.", + ) + parser.add_argument( + "-n", + "--number", + default=3, + help="[optional] Number of previous logged events to display.", ) - parser.add_argument("-n", "--number", default=3, help="[optional] Number of previous logged events to display.") return parser @@ -949,7 +1115,9 @@ def get_rulesets_parser(): ) parser.add_argument("subcommand", help="One of list, add, or remove") parser.add_argument("ruleset", nargs="?", help="Name of RuleSet") - parser.add_argument("rulename", nargs="?", help="Name of Rule to be added or removed") + parser.add_argument( + "rulename", nargs="?", help="Name of Rule to be added or removed" + ) return parser @@ -1004,10 +1172,15 @@ def get_create_rule_template_parser(): def get_create_region_set_parser(): parser = argparse.ArgumentParser( - prog="rdk create-region-set", description="Outputs a YAML region set file for multi-region deployment." + prog="rdk create-region-set", + description="Outputs a YAML region set file for multi-region deployment.", ) parser.add_argument( - "-o", "--output-file", required=False, default="regions", help="Filename of the generated region set file" + "-o", + "--output-file", + required=False, + default="regions", + help="Filename of the generated region set file", ) return parser @@ -1020,7 +1193,9 @@ def parse_region_file(args): region_text = yaml.safe_load(open(args.region_file, "r")) return region_text[region_set] except Exception: - raise SyntaxError(f"Error reading regions: {region_set} in file: {args.region_file}") + raise SyntaxError( + f"Error reading regions: {region_set} in file: {args.region_file}" + ) def run_multi_region(args): @@ -1069,7 +1244,9 @@ def init(self): config_bucket_exists = False if self.args.config_bucket_exists_in_another_account: - print(f"[{my_session.region_name}]: Skipping Config Bucket check due to command line args") + print( + f"[{my_session.region_name}]: Skipping Config Bucket check due to command line args" + ) config_bucket_exists = True config_bucket_name = config_bucket_prefix + "-" + account_id @@ -1082,9 +1259,14 @@ def init(self): control_tower = True if self.args.generate_lambda_layer: - lambda_layer_version = self.__get_existing_lambda_layer(my_session, layer_name=self.args.custom_layer_name) + lambda_layer_version = self.__get_existing_lambda_layer( + my_session, layer_name=self.args.custom_layer_name + ) if lambda_layer_version: - print(f"[{my_session.region_name}]: Found Version: " + lambda_layer_version) + print( + f"[{my_session.region_name}]: Found Version: " + + lambda_layer_version + ) if self.args.generate_lambda_layer: print( f"[{my_session.region_name}]: --generate-lambda-layer Flag received, forcing update of the Lambda Layer in {my_session.region_name}" @@ -1094,8 +1276,12 @@ def init(self): f"[{my_session.region_name}]: Lambda Layer not found in {my_session.region_name}. Creating one now" ) # Try to generate lambda layer with ServerlessAppRepo, manually generate if impossible - self.__create_new_lambda_layer(my_session, layer_name=self.args.custom_layer_name) - lambda_layer_version = self.__get_existing_lambda_layer(my_session, layer_name=self.args.custom_layer_name) + self.__create_new_lambda_layer( + my_session, layer_name=self.args.custom_layer_name + ) + lambda_layer_version = self.__get_existing_lambda_layer( + my_session, layer_name=self.args.custom_layer_name + ) # Check to see if the ConfigRecorder has been created. recorders = my_config.describe_configuration_recorders() @@ -1103,13 +1289,18 @@ def init(self): config_recorder_exists = True config_recorder_name = recorders["ConfigurationRecorders"][0]["name"] config_role_arn = recorders["ConfigurationRecorders"][0]["roleARN"] - print(f"[{my_session.region_name}]: Found Config Recorder: " + config_recorder_name) + print( + f"[{my_session.region_name}]: Found Config Recorder: " + + config_recorder_name + ) print(f"[{my_session.region_name}]: Found Config Role: " + config_role_arn) delivery_channels = my_config.describe_delivery_channels() if len(delivery_channels["DeliveryChannels"]) > 0: delivery_channel_exists = True - config_bucket_name = delivery_channels["DeliveryChannels"][0]["s3BucketName"] + config_bucket_name = delivery_channels["DeliveryChannels"][0][ + "s3BucketName" + ] my_s3 = my_session.client("s3") @@ -1119,18 +1310,26 @@ def init(self): bucket_exists = False for bucket in response["Buckets"]: if bucket["Name"] == config_bucket_name: - print(f"[{my_session.region_name}]: Found Bucket: " + config_bucket_name) + print( + f"[{my_session.region_name}]: Found Bucket: " + + config_bucket_name + ) config_bucket_exists = True bucket_exists = True if not bucket_exists: - print(f"[{my_session.region_name}]: Creating Config bucket " + config_bucket_name) + print( + f"[{my_session.region_name}]: Creating Config bucket " + + config_bucket_name + ) if my_session.region_name == "us-east-1": my_s3.create_bucket(Bucket=config_bucket_name) else: my_s3.create_bucket( Bucket=config_bucket_name, - CreateBucketConfiguration={"LocationConstraint": my_session.region_name}, + CreateBucketConfiguration={ + "LocationConstraint": my_session.region_name + }, ) if not config_role_arn: @@ -1149,29 +1348,49 @@ def init(self): elif partition == "aws-cn": partition_url = ".com.cn" assume_role_policy_template = open( - os.path.join(path.dirname(__file__), "template", assume_role_policy_file), "r" + os.path.join( + path.dirname(__file__), "template", assume_role_policy_file + ), + "r", ).read() - assume_role_policy = json.loads(assume_role_policy_template.replace("${PARTITIONURL}", partition_url)) + assume_role_policy = json.loads( + assume_role_policy_template.replace( + "${PARTITIONURL}", partition_url + ) + ) assume_role_policy["Statement"].append( - {"Effect": "Allow", "Principal": {"AWS": str(account_id)}, "Action": "sts:AssumeRole"} + { + "Effect": "Allow", + "Principal": {"AWS": str(account_id)}, + "Action": "sts:AssumeRole", + } ) my_iam.create_role( - RoleName=config_role_name, AssumeRolePolicyDocument=json.dumps(assume_role_policy), Path="/rdk/" + RoleName=config_role_name, + AssumeRolePolicyDocument=json.dumps(assume_role_policy), + Path="/rdk/", ) # attach role policy my_iam.attach_role_policy( - RoleName=config_role_name, PolicyArn="arn:" + partition + ":iam::aws:policy/service-role/AWS_ConfigRole" + RoleName=config_role_name, + PolicyArn="arn:" + + partition + + ":iam::aws:policy/service-role/AWS_ConfigRole", ) my_iam.attach_role_policy( - RoleName=config_role_name, PolicyArn="arn:" + partition + ":iam::aws:policy/ReadOnlyAccess" + RoleName=config_role_name, + PolicyArn="arn:" + partition + ":iam::aws:policy/ReadOnlyAccess", ) policy_template = open( - os.path.join(path.dirname(__file__), "template", delivery_permission_policy_file), "r" + os.path.join( + path.dirname(__file__), "template", delivery_permission_policy_file + ), + "r", ).read() - delivery_permissions_policy = policy_template.replace("${ACCOUNTID}", account_id).replace( - "${PARTITION}", partition - ) + delivery_permissions_policy = policy_template.replace( + "${ACCOUNTID}", account_id + ).replace("${PARTITION}", partition) my_iam.put_role_policy( RoleName=config_role_name, PolicyName="ConfigDeliveryPermissions", @@ -1184,30 +1403,42 @@ def init(self): # create or update config recorder if not config_role_arn: - config_role_arn = "arn:" + partition + ":iam::" + account_id + ":role/rdk/config-role" + config_role_arn = ( + "arn:" + partition + ":iam::" + account_id + ":role/rdk/config-role" + ) if not control_tower: my_config.put_configuration_recorder( ConfigurationRecorder={ "name": config_recorder_name, "roleARN": config_role_arn, - "recordingGroup": {"allSupported": True, "includeGlobalResourceTypes": True}, + "recordingGroup": { + "allSupported": True, + "includeGlobalResourceTypes": True, + }, } ) if not delivery_channel_exists: # create delivery channel - print(f"[{my_session.region_name}]: Creating delivery channel to bucket " + config_bucket_name) + print( + f"[{my_session.region_name}]: Creating delivery channel to bucket " + + config_bucket_name + ) my_config.put_delivery_channel( DeliveryChannel={ "name": "default", "s3BucketName": config_bucket_name, - "configSnapshotDeliveryProperties": {"deliveryFrequency": "Six_Hours"}, + "configSnapshotDeliveryProperties": { + "deliveryFrequency": "Six_Hours" + }, } ) # start config recorder - my_config.start_configuration_recorder(ConfigurationRecorderName=config_recorder_name) + my_config.start_configuration_recorder( + ConfigurationRecorderName=config_recorder_name + ) print(f"[{my_session.region_name}]: Config Service is ON") else: print( @@ -1217,26 +1448,39 @@ def init(self): print(f"[{my_session.region_name}]: Config setup complete.") # create code bucket - code_bucket_name = code_bucket_prefix + account_id + "-" + my_session.region_name + code_bucket_name = ( + code_bucket_prefix + account_id + "-" + my_session.region_name + ) response = my_s3.list_buckets() bucket_exists = False for bucket in response["Buckets"]: if bucket["Name"] == code_bucket_name: bucket_exists = True - print(f"[{my_session.region_name}]: Found code bucket: " + code_bucket_name) + print( + f"[{my_session.region_name}]: Found code bucket: " + + code_bucket_name + ) if not bucket_exists: if self.args.skip_code_bucket_creation: - print(f"[{my_session.region_name}]: Skipping Code Bucket creation due to command line args") + print( + f"[{my_session.region_name}]: Skipping Code Bucket creation due to command line args" + ) else: - print(f"[{my_session.region_name}]: Creating Code bucket " + code_bucket_name) + print( + f"[{my_session.region_name}]: Creating Code bucket " + + code_bucket_name + ) # Consideration for us-east-1 S3 API if my_session.region_name == "us-east-1": my_s3.create_bucket(Bucket=code_bucket_name) else: my_s3.create_bucket( - Bucket=code_bucket_name, CreateBucketConfiguration={"LocationConstraint": my_session.region_name} + Bucket=code_bucket_name, + CreateBucketConfiguration={ + "LocationConstraint": my_session.region_name + }, ) return 0 @@ -1283,10 +1527,14 @@ def clean(self): try: # First delete the Config Recorder itself. Do we need to stop it first? Let's stop it just to be safe. my_config.stop_configuration_recorder( - ConfigurationRecorderName=recorders["ConfigurationRecorders"][0]["name"] + ConfigurationRecorderName=recorders["ConfigurationRecorders"][0][ + "name" + ] ) my_config.delete_configuration_recorder( - ConfigurationRecorderName=recorders["ConfigurationRecorders"][0]["name"] + ConfigurationRecorderName=recorders["ConfigurationRecorders"][0][ + "name" + ] ) except Exception as e: print("Error encountered removing Configuration Recorder: " + str(e)) @@ -1296,13 +1544,21 @@ def clean(self): try: response = iam_client.get_role(RoleName=config_role_name) try: - role_policy_results = iam_client.list_role_policies(RoleName=config_role_name) + role_policy_results = iam_client.list_role_policies( + RoleName=config_role_name + ) for policy_name in role_policy_results["PolicyNames"]: - iam_client.delete_role_policy(RoleName=config_role_name, PolicyName=policy_name) + iam_client.delete_role_policy( + RoleName=config_role_name, PolicyName=policy_name + ) - role_policy_results = iam_client.list_attached_role_policies(RoleName=config_role_name) + role_policy_results = iam_client.list_attached_role_policies( + RoleName=config_role_name + ) for policy in role_policy_results["AttachedPolicies"]: - iam_client.detach_role_policy(RoleName=config_role_name, PolicyArn=policy["PolicyArn"]) + iam_client.detach_role_policy( + RoleName=config_role_name, PolicyArn=policy["PolicyArn"] + ) # Once all policies are detached we should be able to delete the Role. iam_client.delete_role(RoleName=config_role_name) @@ -1315,11 +1571,17 @@ def clean(self): delivery_channels = my_config.describe_delivery_channels() if len(delivery_channels["DeliveryChannels"]) > 0: for delivery_channel in delivery_channels["DeliveryChannels"]: - config_bucket_names.append(delivery_channels["DeliveryChannels"][0]["s3BucketName"]) + config_bucket_names.append( + delivery_channels["DeliveryChannels"][0]["s3BucketName"] + ) try: - my_config.delete_delivery_channel(DeliveryChannelName=delivery_channel["name"]) + my_config.delete_delivery_channel( + DeliveryChannelName=delivery_channel["name"] + ) except Exception as e: - print("Error encountered trying to delete Delivery Channel: " + str(e)) + print( + "Error encountered trying to delete Delivery Channel: " + str(e) + ) if config_bucket_names: # empty and then delete the config bucket. @@ -1353,7 +1615,9 @@ def clean(self): print("Error encountered deleting Functions stack: " + str(e)) # Delete the code bucket, if one exists. - code_bucket_name = code_bucket_prefix + account_id + "-" + my_session.region_name + code_bucket_name = ( + code_bucket_prefix + account_id + "-" + my_session.region_name + ) try: code_bucket = my_session.resource("s3").Bucket(code_bucket_name) code_bucket.objects.all().delete() @@ -1380,16 +1644,14 @@ def create(self): extension_mapping = { "java8": ".java", - "python3.6-managed": ".py", "python3.7": ".py", "python3.7-lib": ".py", "python3.8": ".py", "python3.8-lib": ".py", "python3.9": ".py", "python3.9-lib": ".py", - "nodejs6.10": ".js", - "dotnetcore1.0": "cs", - "dotnetcore2.0": "cs", + "python3.10": ".py", + "python3.10-lib": ".py", } if self.args.runtime not in extension_mapping: print("rdk does not support that runtime yet.") @@ -1411,8 +1673,6 @@ def create(self): # copy rule template into rule directory if self.args.runtime == "java8": self.__create_java_rule() - elif self.args.runtime in ["dotnetcore1.0", "dotnetcore2.0"]: - self.__create_dotnet_rule() else: src = os.path.join( path.dirname(__file__), @@ -1430,18 +1690,32 @@ def create(self): shutil.copyfile(src, dst) f = fileinput.input(files=dst, inplace=True) for line in f: - if self.args.runtime in ["python3.7-lib", "python3.8-lib", "python3.9-lib"]: + if self.args.runtime in [ + "python3.7-lib", + "python3.8-lib", + "python3.9-lib", + "python3.10-lib", + ]: if self.args.resource_types: applicable_resource_list = "" - for resource_type in self.args.resource_types.split(","): - applicable_resource_list += "'" + resource_type + "', " + for resource_type in self.args.resource_types.split( + "," + ): + applicable_resource_list += ( + "'" + resource_type + "', " + ) print( line.replace("<%RuleName%>", self.args.rulename) .replace( "<%ApplicableResources1%>", - "\nAPPLICABLE_RESOURCES = [" + applicable_resource_list[:-2] + "]\n", + "\nAPPLICABLE_RESOURCES = [" + + applicable_resource_list[:-2] + + "]\n", ) - .replace("<%ApplicableResources2%>", ", APPLICABLE_RESOURCES"), + .replace( + "<%ApplicableResources2%>", + ", APPLICABLE_RESOURCES", + ), end="", ) else: @@ -1452,7 +1726,9 @@ def create(self): end="", ) else: - print(line.replace("<%RuleName%>", self.args.rulename), end="") + print( + line.replace("<%RuleName%>", self.args.rulename), end="" + ) f.close() src = os.path.join( @@ -1467,12 +1743,16 @@ def create(self): os.getcwd(), rules_dir, self.args.rulename, - self.args.rulename + "_test" + extension_mapping[self.args.runtime], + self.args.rulename + + "_test" + + extension_mapping[self.args.runtime], ) shutil.copyfile(src, dst) f = fileinput.input(files=dst, inplace=True) for line in f: - print(line.replace("<%RuleName%>", self.args.rulename), end="") + print( + line.replace("<%RuleName%>", self.args.rulename), end="" + ) f.close() src = os.path.join( @@ -1548,10 +1828,18 @@ def modify(self): self.args.remediation_concurrent_execution_percent = ssm_controls.get( "ConcurrentExecutionRatePercentage", "" ) - self.args.remediation_error_rate_percent = ssm_controls.get("ErrorPercentage", "") - self.args.remediation_parameters = json.dumps(params["Parameters"]) if params.get("Parameters") else None - self.args.auto_remediation_retry_attempts = params.get("MaximumAutomaticAttempts", "") - self.args.auto_remediation_retry_time = params.get("RetryAttemptSeconds", "") + self.args.remediation_error_rate_percent = ssm_controls.get( + "ErrorPercentage", "" + ) + self.args.remediation_parameters = ( + json.dumps(params["Parameters"]) if params.get("Parameters") else None + ) + self.args.auto_remediation_retry_attempts = params.get( + "MaximumAutomaticAttempts", "" + ) + self.args.auto_remediation_retry_time = params.get( + "RetryAttemptSeconds", "" + ) self.args.remediation_action = params.get("TargetId", "") self.args.remediation_action_version = params.get("TargetVersion", "") @@ -1562,7 +1850,11 @@ def modify(self): # Write the parameters to a file in the rule directory. self.__populate_params() - print("Modified Rule '" + self.args.rulename + "'. Use the `deploy` command to push your changes to AWS.") + print( + "Modified Rule '" + + self.args.rulename + + "'. Use the `deploy` command to push your changes to AWS." + ) def undeploy(self): self.__parse_deploy_args(ForceArgument=True) @@ -1570,7 +1862,9 @@ def undeploy(self): if not self.args.force: confirmation = False while not confirmation: - my_input = input("Delete specified Rules and Lambda Functions from your AWS Account? (y/N): ") + my_input = input( + "Delete specified Rules and Lambda Functions from your AWS Account? (y/N): " + ) if my_input.lower() == "y": confirmation = True if my_input.lower() == "n" or my_input == "": @@ -1608,7 +1902,9 @@ def undeploy(self): for rule_name in rule_names: try: - cfn_client.delete_stack(StackName=self.__get_stack_name_from_rule_name(rule_name)) + cfn_client.delete_stack( + StackName=self.__get_stack_name_from_rule_name(rule_name) + ) deleted_stacks.append(self.__get_stack_name_from_rule_name(rule_name)) except ClientError as ce: print( @@ -1621,12 +1917,16 @@ def undeploy(self): + str(e) ) - print(f"[{my_session.region_name}]: Rule removal initiated. Waiting for Stack Deletion to complete.") + print( + f"[{my_session.region_name}]: Rule removal initiated. Waiting for Stack Deletion to complete." + ) for stack_name in deleted_stacks: self.__wait_for_cfn_stack(cfn_client, stack_name) - print(f"[{my_session.region_name}]: Rule removal complete, but local files have been preserved.") + print( + f"[{my_session.region_name}]: Rule removal complete, but local files have been preserved." + ) print(f"[{my_session.region_name}]: To re-deploy, use the 'deploy' command.") def undeploy_organization(self): @@ -1635,7 +1935,9 @@ def undeploy_organization(self): if not self.args.force: confirmation = False while not confirmation: - my_input = input("Delete specified Rules and Lambda Functions from your Organization? (y/N): ") + my_input = input( + "Delete specified Rules and Lambda Functions from your Organization? (y/N): " + ) if my_input.lower() == "y": confirmation = True if my_input.lower() == "n" or my_input == "": @@ -1673,7 +1975,9 @@ def undeploy_organization(self): for rule_name in rule_names: try: - cfn_client.delete_stack(StackName=self.__get_stack_name_from_rule_name(rule_name)) + cfn_client.delete_stack( + StackName=self.__get_stack_name_from_rule_name(rule_name) + ) deleted_stacks.append(self.__get_stack_name_from_rule_name(rule_name)) except ClientError as ce: print( @@ -1686,13 +1990,19 @@ def undeploy_organization(self): + str(e) ) - print(f"[{my_session.region_name}]: Rule removal initiated. Waiting for Stack Deletion to complete.") + print( + f"[{my_session.region_name}]: Rule removal initiated. Waiting for Stack Deletion to complete." + ) for stack_name in deleted_stacks: self.__wait_for_cfn_stack(cfn_client, stack_name) - print(f"[{my_session.region_name}]: Rule removal complete, but local files have been preserved.") - print(f"[{my_session.region_name}]: To re-deploy, use the 'deploy-organization' command.") + print( + f"[{my_session.region_name}]: Rule removal complete, but local files have been preserved." + ) + print( + f"[{my_session.region_name}]: To re-deploy, use the 'deploy-organization' command." + ) def deploy(self): self.__parse_deploy_args() @@ -1713,7 +2023,9 @@ def deploy(self): if self.args.custom_code_bucket: code_bucket_name = self.args.custom_code_bucket else: - code_bucket_name = code_bucket_prefix + account_id + "-" + my_session.region_name + code_bucket_name = ( + code_bucket_prefix + account_id + "-" + my_session.region_name + ) # If we're only deploying the Lambda functions (and role + permissions), branch here. Someday the "main" execution path should use the same generated CFN templates for single-account deployment. if self.args.functions_only: @@ -1741,7 +2053,9 @@ def deploy(self): for rule_name in rule_names: rule_params, cfn_tags = self.__get_rule_parameters(rule_name) if "SourceIdentifier" in rule_params: - print(f"[{my_session.region_name}]: Skipping code packaging for Managed Rule.") + print( + f"[{my_session.region_name}]: Skipping code packaging for Managed Rule." + ) else: s3_dst = self.__upload_function_code( rule_name, rule_params, account_id, my_session, code_bucket_name @@ -1754,7 +2068,12 @@ def deploy(self): config = my_s3_client._client_config config.signature_version = botocore.UNSIGNED template_url = boto3.client("s3", config=config).generate_presigned_url( - "get_object", ExpiresIn=0, Params={"Bucket": code_bucket_name, "Key": self.args.stack_name + ".json"} + "get_object", + ExpiresIn=0, + Params={ + "Bucket": code_bucket_name, + "Key": self.args.stack_name + ".json", + }, ) # Check if stack exists. If it does, update it. If it doesn't, create it. @@ -1763,9 +2082,10 @@ def deploy(self): my_stack = my_cfn.describe_stacks(StackName=self.args.stack_name) # If we've gotten here, stack exists and we should update it. - print(f"[{my_session.region_name}]: Updating CloudFormation Stack for Lambda functions.") + print( + f"[{my_session.region_name}]: Updating CloudFormation Stack for Lambda functions." + ) try: - cfn_args = { "StackName": self.args.stack_name, "TemplateURL": template_url, @@ -1785,7 +2105,9 @@ def deploy(self): if e.response["Error"]["Code"] == "ValidationError": if "No updates are to be performed." in str(e): # No changes made to Config rule definition, so CloudFormation won't do anything. - print(f"[{my_session.region_name}]: No changes to Config Rule configurations.") + print( + f"[{my_session.region_name}]: No changes to Config Rule configurations." + ) else: # Something unexpected has gone wrong. Emit an error and bail. print(f"[{my_session.region_name}]: {e}") @@ -1797,10 +2119,16 @@ def deploy(self): for rule_name in rule_names: rule_params, cfn_tags = self.__get_rule_parameters(rule_name) my_lambda_arn = self.__get_lambda_arn_for_rule( - rule_name, partition, my_session.region_name, account_id, rule_params + rule_name, + partition, + my_session.region_name, + account_id, + rule_params, ) if "SourceIdentifier" in rule_params: - print(f"[{my_session.region_name}]: Skipping Lambda upload for Managed Rule.") + print( + f"[{my_session.region_name}]: Skipping Lambda upload for Managed Rule." + ) continue print(f"[{my_session.region_name}]: Publishing Lambda code...") @@ -1812,9 +2140,11 @@ def deploy(self): Publish=True, ) print(f"[{my_session.region_name}]: Lambda code updated.") - except ClientError as e: + except ClientError: # If we're in the exception, the stack does not exist and we should create it. - print(f"[{my_session.region_name}]: Creating CloudFormation Stack for Lambda Functions.") + print( + f"[{my_session.region_name}]: Creating CloudFormation Stack for Lambda Functions." + ) cfn_args = { "StackName": self.args.stack_name, @@ -1850,7 +2180,9 @@ def deploy(self): combined_input_parameters = {} if "InputParameters" in rule_params: - combined_input_parameters.update(json.loads(rule_params["InputParameters"])) + combined_input_parameters.update( + json.loads(rule_params["InputParameters"]) + ) if "OptionalParameters" in rule_params: # Remove empty parameters @@ -1892,65 +2224,112 @@ def deploy(self): "ParameterKey": "SourceInputParameters", "ParameterValue": json.dumps(combined_input_parameters), }, - {"ParameterKey": "SourceIdentifier", "ParameterValue": rule_params["SourceIdentifier"]}, + { + "ParameterKey": "SourceIdentifier", + "ParameterValue": rule_params["SourceIdentifier"], + }, ] my_cfn = my_session.client("cloudformation") if "Remediation" in rule_params: - print(f"[{my_session.region_name}]: Build The CFN Template with Remediation Settings") - cfn_body = os.path.join(path.dirname(__file__), "template", "configManagedRuleWithRemediation.json") + print( + f"[{my_session.region_name}]: Build The CFN Template with Remediation Settings" + ) + cfn_body = os.path.join( + path.dirname(__file__), + "template", + "configManagedRuleWithRemediation.json", + ) template_body = open(cfn_body, "r").read() json_body = json.loads(template_body) - remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) + remediation = self.__create_remediation_cloudformation_block( + rule_params["Remediation"] + ) json_body["Resources"]["Remediation"] = remediation if "SSMAutomation" in rule_params: # Reference the SSM Automation Role Created, if IAM is created - print(f"[{my_session.region_name}]: Building SSM Automation Section") + print( + f"[{my_session.region_name}]: Building SSM Automation Section" + ) ssm_automation = self.__create_automation_cloudformation_block( - rule_params["SSMAutomation"], self.__get_alphanumeric_rule_name(rule_name) + rule_params["SSMAutomation"], + self.__get_alphanumeric_rule_name(rule_name), ) json_body["Resources"][ - self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") + self.__get_alphanumeric_rule_name( + rule_name + "RemediationAction" + ) ] = ssm_automation if "IAM" in rule_params["SSMAutomation"]: - print(f"[{my_session.region_name}]: Lets Build IAM Role and Policy") + print( + f"[{my_session.region_name}]: Lets Build IAM Role and Policy" + ) # TODO Check For IAM Settings - json_body["Resources"]["Remediation"]["Properties"]["Parameters"]["AutomationAssumeRole"][ - "StaticValue" - ]["Values"] = [ - {"Fn::GetAtt": [self.__get_alphanumeric_rule_name(rule_name + "Role"), "Arn"]} + json_body["Resources"]["Remediation"]["Properties"][ + "Parameters" + ]["AutomationAssumeRole"]["StaticValue"]["Values"] = [ + { + "Fn::GetAtt": [ + self.__get_alphanumeric_rule_name( + rule_name + "Role" + ), + "Arn", + ] + } ] - ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block( - rule_params["SSMAutomation"], self.__get_alphanumeric_rule_name(rule_name) + ( + ssm_iam_role, + ssm_iam_policy, + ) = self.__create_automation_iam_cloudformation_block( + rule_params["SSMAutomation"], + self.__get_alphanumeric_rule_name(rule_name), ) - json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name + "Role")] = ssm_iam_role + json_body["Resources"][ + self.__get_alphanumeric_rule_name(rule_name + "Role") + ] = ssm_iam_role json_body["Resources"][ self.__get_alphanumeric_rule_name(rule_name + "Policy") ] = ssm_iam_policy - print(f"[{my_session.region_name}]: Build Supporting SSM Resources") + print( + f"[{my_session.region_name}]: Build Supporting SSM Resources" + ) resource_depends_on = [ "rdkConfigRule", - self.__get_alphanumeric_rule_name(rule_name + "RemediationAction"), + self.__get_alphanumeric_rule_name( + rule_name + "RemediationAction" + ), ] # Builds SSM Document Before Config RUle - json_body["Resources"]["Remediation"]["DependsOn"] = resource_depends_on - json_body["Resources"]["Remediation"]["Properties"]["TargetId"] = { - "Ref": self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") + json_body["Resources"]["Remediation"][ + "DependsOn" + ] = resource_depends_on + json_body["Resources"]["Remediation"]["Properties"][ + "TargetId" + ] = { + "Ref": self.__get_alphanumeric_rule_name( + rule_name + "RemediationAction" + ) } try: my_stack_name = self.__get_stack_name_from_rule_name(rule_name) my_stack = my_cfn.describe_stacks(StackName=my_stack_name) # If we've gotten here, stack exists and we should update it. - print(f"[{my_session.region_name}]: Updating CloudFormation Stack for " + rule_name) + print( + f"[{my_session.region_name}]: Updating CloudFormation Stack for " + + rule_name + ) try: cfn_args = { "StackName": my_stack_name, "TemplateBody": json.dumps(json_body, indent=2), "Parameters": my_params, - "Capabilities": ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + ], } # If no tags key is specified, or if the tags dict is empty @@ -1962,23 +2341,31 @@ def deploy(self): if e.response["Error"]["Code"] == "ValidationError": if "No updates are to be performed." in str(e): # No changes made to Config rule definition, so CloudFormation won't do anything. - print(f"[{my_session.region_name}]: No changes to Config Rule.") + print( + f"[{my_session.region_name}]: No changes to Config Rule." + ) else: # Something unexpected has gone wrong. Emit an error and bail. print(f"[{my_session.region_name}]: {e}") return 1 else: raise - except ClientError as e: + except ClientError: # If we're in the exception, the stack does not exist and we should create it. - print(f"[{my_session.region_name}]: Creating CloudFormation Stack for " + rule_name) + print( + f"[{my_session.region_name}]: Creating CloudFormation Stack for " + + rule_name + ) if "Remediation" in rule_params: cfn_args = { "StackName": my_stack_name, "TemplateBody": json.dumps(json_body, indent=2), "Parameters": my_params, - "Capabilities": ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + ], } else: @@ -1999,13 +2386,18 @@ def deploy(self): else: # deploy config rule - cfn_body = os.path.join(path.dirname(__file__), "template", "configManagedRule.json") + cfn_body = os.path.join( + path.dirname(__file__), "template", "configManagedRule.json" + ) try: my_stack_name = self.__get_stack_name_from_rule_name(rule_name) my_stack = my_cfn.describe_stacks(StackName=my_stack_name) # If we've gotten here, stack exists and we should update it. - print(f"[{my_session.region_name}]: Updating CloudFormation Stack for " + rule_name) + print( + f"[{my_session.region_name}]: Updating CloudFormation Stack for " + + rule_name + ) try: cfn_args = { "StackName": my_stack_name, @@ -2022,7 +2414,9 @@ def deploy(self): if e.response["Error"]["Code"] == "ValidationError": if "No updates are to be performed." in str(e): # No changes made to Config rule definition, so CloudFormation won't do anything. - print(f"[{my_session.region_name}]: No changes to Config Rule.") + print( + f"[{my_session.region_name}]: No changes to Config Rule." + ) else: # Something unexpected has gone wrong. Emit an error and bail. print(f"[{my_session.region_name}]: {e}") @@ -2031,7 +2425,10 @@ def deploy(self): raise except ClientError as e: # If we're in the exception, the stack does not exist and we should create it. - print(f"[{my_session.region_name}]: Creating CloudFormation Stack for " + rule_name) + print( + f"[{my_session.region_name}]: Creating CloudFormation Stack for " + + rule_name + ) cfn_args = { "StackName": my_stack_name, "TemplateBody": open(cfn_body, "r").read(), @@ -2055,20 +2452,31 @@ def deploy(self): print(f"[{my_session.region_name}]: Found Custom Rule.") s3_src = "" - s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name) + s3_dst = self.__upload_function_code( + rule_name, rule_params, account_id, my_session, code_bucket_name + ) # create CFN Parameters for Custom Rules lambdaRoleArn = "" if self.args.lambda_role_arn: - print(f"[{my_session.region_name}]: Existing IAM Role provided: " + self.args.lambda_role_arn) + print( + f"[{my_session.region_name}]: Existing IAM Role provided: " + + self.args.lambda_role_arn + ) lambdaRoleArn = self.args.lambda_role_arn elif self.args.lambda_role_name: - print(f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + self.args.lambda_role_name) + print( + f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + + self.args.lambda_role_name + ) arn = f"arn:{partition}:iam::{account_id}:role/{self.args.lambda_role_name}" lambdaRoleArn = arn if self.args.boundary_policy_arn: - print(f"[{my_session.region_name}]: Boundary Policy provided: " + self.args.boundary_policy_arn) + print( + f"[{my_session.region_name}]: Boundary Policy provided: " + + self.args.boundary_policy_arn + ) boundaryPolicyArn = self.args.boundary_policy_arn else: boundaryPolicyArn = "" @@ -2123,8 +2531,14 @@ def deploy(self): "ParameterKey": "SourceInputParameters", "ParameterValue": json.dumps(combined_input_parameters), }, - {"ParameterKey": "SourceHandler", "ParameterValue": self.__get_handler(rule_name, rule_params)}, - {"ParameterKey": "Timeout", "ParameterValue": str(self.args.lambda_timeout)}, + { + "ParameterKey": "SourceHandler", + "ParameterValue": self.__get_handler(rule_name, rule_params), + }, + { + "ParameterKey": "Timeout", + "ParameterValue": str(self.args.lambda_timeout), + }, ] layers = self.__get_lambda_layers(my_session, self.args, rule_params) @@ -2133,55 +2547,89 @@ def deploy(self): layers.extend(additional_layers) if layers: - my_params.append({"ParameterKey": "Layers", "ParameterValue": ",".join(layers)}) + my_params.append( + {"ParameterKey": "Layers", "ParameterValue": ",".join(layers)} + ) if self.args.lambda_security_groups and self.args.lambda_subnets: my_params.append( - {"ParameterKey": "SecurityGroupIds", "ParameterValue": self.args.lambda_security_groups} + { + "ParameterKey": "SecurityGroupIds", + "ParameterValue": self.args.lambda_security_groups, + } + ) + my_params.append( + { + "ParameterKey": "SubnetIds", + "ParameterValue": self.args.lambda_subnets, + } ) - my_params.append({"ParameterKey": "SubnetIds", "ParameterValue": self.args.lambda_subnets}) # create json of CFN template - cfn_body = os.path.join(path.dirname(__file__), "template", "configRule.json") + cfn_body = os.path.join( + path.dirname(__file__), "template", "configRule.json" + ) template_body = open(cfn_body, "r").read() json_body = json.loads(template_body) remediation = "" if "Remediation" in rule_params: - remediation = self.__create_remediation_cloudformation_block(rule_params["Remediation"]) + remediation = self.__create_remediation_cloudformation_block( + rule_params["Remediation"] + ) json_body["Resources"]["Remediation"] = remediation if "SSMAutomation" in rule_params: ##AWS needs to build the SSM before the Config Rule resource_depends_on = [ "rdkConfigRule", - self.__get_alphanumeric_rule_name(rule_name + "RemediationAction"), + self.__get_alphanumeric_rule_name( + rule_name + "RemediationAction" + ), ] remediation["DependsOn"] = resource_depends_on # Add JSON Reference to SSM Document { "Ref" : "MyEC2Instance" } remediation["Properties"]["TargetId"] = { - "Ref": self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") + "Ref": self.__get_alphanumeric_rule_name( + rule_name + "RemediationAction" + ) } if "SSMAutomation" in rule_params: print(f"[{my_session.region_name}]: Building SSM Automation Section") - ssm_automation = self.__create_automation_cloudformation_block(rule_params["SSMAutomation"], rule_name) + ssm_automation = self.__create_automation_cloudformation_block( + rule_params["SSMAutomation"], rule_name + ) json_body["Resources"][ self.__get_alphanumeric_rule_name(rule_name + "RemediationAction") ] = ssm_automation if "IAM" in rule_params["SSMAutomation"]: print("Lets Build IAM Role and Policy") # TODO Check For IAM Settings - json_body["Resources"]["Remediation"]["Properties"]["Parameters"]["AutomationAssumeRole"][ - "StaticValue" - ]["Values"] = [{"Fn::GetAtt": [self.__get_alphanumeric_rule_name(rule_name + "Role"), "Arn"]}] + json_body["Resources"]["Remediation"]["Properties"]["Parameters"][ + "AutomationAssumeRole" + ]["StaticValue"]["Values"] = [ + { + "Fn::GetAtt": [ + self.__get_alphanumeric_rule_name(rule_name + "Role"), + "Arn", + ] + } + ] - ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block( + ( + ssm_iam_role, + ssm_iam_policy, + ) = self.__create_automation_iam_cloudformation_block( rule_params["SSMAutomation"], rule_name ) - json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name + "Role")] = ssm_iam_role - json_body["Resources"][self.__get_alphanumeric_rule_name(rule_name + "Policy")] = ssm_iam_policy + json_body["Resources"][ + self.__get_alphanumeric_rule_name(rule_name + "Role") + ] = ssm_iam_role + json_body["Resources"][ + self.__get_alphanumeric_rule_name(rule_name + "Policy") + ] = ssm_iam_policy # debugging # print(json.dumps(json_body, indent=2)) @@ -2192,7 +2640,10 @@ def deploy(self): my_stack_name = self.__get_stack_name_from_rule_name(rule_name) my_stack = my_cfn.describe_stacks(StackName=my_stack_name) # If we've gotten here, stack exists and we should update it. - print(f"[{my_session.region_name}]: Updating CloudFormation Stack for " + rule_name) + print( + f"[{my_session.region_name}]: Updating CloudFormation Stack for " + + rule_name + ) try: cfn_args = { "StackName": my_stack_name, @@ -2208,14 +2659,21 @@ def deploy(self): response = my_cfn.update_stack(**cfn_args) except ClientError as e: if e.response["Error"]["Code"] == "ValidationError": - if "No updates are to be performed." in str(e): # No changes made to Config rule definition, so CloudFormation won't do anything. - print(f"[{my_session.region_name}]: No changes to Config Rule.") + print( + f"[{my_session.region_name}]: No changes to Config Rule." + ) else: # Something unexpected has gone wrong. Emit an error and bail. - print(f"[{my_session.region_name}]: Validation Error on CFN\n") - print(f"[{my_session.region_name}]: " + json.dumps(cfn_args) + "\n") + print( + f"[{my_session.region_name}]: Validation Error on CFN\n" + ) + print( + f"[{my_session.region_name}]: " + + json.dumps(cfn_args) + + "\n" + ) print(f"[{my_session.region_name}]: {e}\n") return 1 else: @@ -2226,12 +2684,18 @@ def deploy(self): print(f"[{my_session.region_name}]: Publishing Lambda code...") my_lambda_client = my_session.client("lambda") my_lambda_client.update_function_code( - FunctionName=my_lambda_arn, S3Bucket=code_bucket_name, S3Key=s3_dst, Publish=True + FunctionName=my_lambda_arn, + S3Bucket=code_bucket_name, + S3Key=s3_dst, + Publish=True, ) print(f"[{my_session.region_name}]: Lambda code updated.") except ClientError as e: # If we're in the exception, the stack does not exist and we should create it. - print(f"[{my_session.region_name}]: Creating CloudFormation Stack for " + rule_name) + print( + f"[{my_session.region_name}]: Creating CloudFormation Stack for " + + rule_name + ) cfn_args = { "StackName": my_stack_name, "TemplateBody": json.dumps(json_body, indent=2), @@ -2275,7 +2739,9 @@ def deploy_organization(self): if self.args.custom_code_bucket: code_bucket_name = self.args.custom_code_bucket else: - code_bucket_name = code_bucket_prefix + account_id + "-" + my_session.region_name + code_bucket_name = ( + code_bucket_prefix + account_id + "-" + my_session.region_name + ) # If we're only deploying the Lambda functions (and role + permissions), branch here. Someday the "main" execution path should use the same generated CFN templates for single-account deployment. if self.args.functions_only: @@ -2302,7 +2768,9 @@ def deploy_organization(self): combined_input_parameters = {} if "InputParameters" in rule_params: - combined_input_parameters.update(json.loads(rule_params["InputParameters"])) + combined_input_parameters.update( + json.loads(rule_params["InputParameters"]) + ) if "OptionalParameters" in rule_params: # Remove empty parameters @@ -2344,12 +2812,19 @@ def deploy_organization(self): "ParameterKey": "SourceInputParameters", "ParameterValue": json.dumps(combined_input_parameters), }, - {"ParameterKey": "SourceIdentifier", "ParameterValue": rule_params["SourceIdentifier"]}, + { + "ParameterKey": "SourceIdentifier", + "ParameterValue": rule_params["SourceIdentifier"], + }, ] my_cfn = my_session.client("cloudformation") # deploy config rule - cfn_body = os.path.join(path.dirname(__file__), "template", "configManagedRuleOrganization.json") + cfn_body = os.path.join( + path.dirname(__file__), + "template", + "configManagedRuleOrganization.json", + ) try: my_stack_name = self.__get_stack_name_from_rule_name(rule_name) @@ -2407,7 +2882,9 @@ def deploy_organization(self): print("Found Custom Rule.") s3_src = "" - s3_dst = self.__upload_function_code(rule_name, rule_params, account_id, my_session, code_bucket_name) + s3_dst = self.__upload_function_code( + rule_name, rule_params, account_id, my_session, code_bucket_name + ) # create CFN Parameters for Custom Rules lambdaRoleArn = "" @@ -2415,7 +2892,10 @@ def deploy_organization(self): print("Existing IAM Role provided: " + self.args.lambda_role_arn) lambdaRoleArn = self.args.lambda_role_arn elif self.args.lambda_role_name: - print(f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + self.args.lambda_role_name) + print( + f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + + self.args.lambda_role_name + ) arn = f"arn:{partition}:iam::{account_id}:role/{self.args.lambda_role_name}" lambdaRoleArn = arn @@ -2475,8 +2955,14 @@ def deploy_organization(self): "ParameterKey": "SourceInputParameters", "ParameterValue": json.dumps(combined_input_parameters), }, - {"ParameterKey": "SourceHandler", "ParameterValue": self.__get_handler(rule_name, rule_params)}, - {"ParameterKey": "Timeout", "ParameterValue": str(self.args.lambda_timeout)}, + { + "ParameterKey": "SourceHandler", + "ParameterValue": self.__get_handler(rule_name, rule_params), + }, + { + "ParameterKey": "Timeout", + "ParameterValue": str(self.args.lambda_timeout), + }, ] layers = self.__get_lambda_layers(my_session, self.args, rule_params) @@ -2485,16 +2971,28 @@ def deploy_organization(self): layers.extend(additional_layers) if layers: - my_params.append({"ParameterKey": "Layers", "ParameterValue": ",".join(layers)}) + my_params.append( + {"ParameterKey": "Layers", "ParameterValue": ",".join(layers)} + ) if self.args.lambda_security_groups and self.args.lambda_subnets: my_params.append( - {"ParameterKey": "SecurityGroupIds", "ParameterValue": self.args.lambda_security_groups} + { + "ParameterKey": "SecurityGroupIds", + "ParameterValue": self.args.lambda_security_groups, + } + ) + my_params.append( + { + "ParameterKey": "SubnetIds", + "ParameterValue": self.args.lambda_subnets, + } ) - my_params.append({"ParameterKey": "SubnetIds", "ParameterValue": self.args.lambda_subnets}) # create json of CFN template - cfn_body = os.path.join(path.dirname(__file__), "template", "configRuleOrganization.json") + cfn_body = os.path.join( + path.dirname(__file__), "template", "configRuleOrganization.json" + ) template_body = open(cfn_body, "r").read() json_body = json.loads(template_body) @@ -2523,7 +3021,6 @@ def deploy_organization(self): response = my_cfn.update_stack(**cfn_args) except ClientError as e: if e.response["Error"]["Code"] == "ValidationError": - if "No updates are to be performed." in str(e): # No changes made to Config rule definition, so CloudFormation won't do anything. print("No changes to Config Rule.") @@ -2541,7 +3038,10 @@ def deploy_organization(self): print("Publishing Lambda code...") my_lambda_client = my_session.client("lambda") my_lambda_client.update_function_code( - FunctionName=my_lambda_arn, S3Bucket=code_bucket_name, S3Key=s3_dst, Publish=True + FunctionName=my_lambda_arn, + S3Bucket=code_bucket_name, + S3Key=s3_dst, + Publish=True, ) print("Lambda code updated.") except ClientError as e: @@ -2573,7 +3073,6 @@ def deploy_organization(self): return 0 def export(self): - self.__parse_export_args() # get the rule names @@ -2600,7 +3099,9 @@ def export(self): combined_input_parameters = {} if "InputParameters" in rule_params: - combined_input_parameters.update(json.loads(rule_params["InputParameters"])) + combined_input_parameters.update( + json.loads(rule_params["InputParameters"]) + ) if "OptionalParameters" in rule_params: # Remove empty parameters @@ -2654,22 +3155,36 @@ def export(self): "lambda_timeout": str(self.args.lambda_timeout), } - params_file_path = os.path.join(os.getcwd(), rules_dir, rule_name, rule_name.lower() + ".tfvars.json") + params_file_path = os.path.join( + os.getcwd(), rules_dir, rule_name, rule_name.lower() + ".tfvars.json" + ) parameters_file = open(params_file_path, "w") json.dump(my_params, parameters_file, indent=4) parameters_file.close() # create json of CFN template print(self.args.format + " version: " + self.args.version) tf_file_body = os.path.join( - path.dirname(__file__), "template", self.args.format, self.args.version, "config_rule.tf" + path.dirname(__file__), + "template", + self.args.format, + self.args.version, + "config_rule.tf", + ) + tf_file_path = os.path.join( + os.getcwd(), rules_dir, rule_name, rule_name.lower() + "_rule.tf" ) - tf_file_path = os.path.join(os.getcwd(), rules_dir, rule_name, rule_name.lower() + "_rule.tf") shutil.copy(tf_file_body, tf_file_path) variables_file_body = os.path.join( - path.dirname(__file__), "template", self.args.format, self.args.version, "variables.tf" + path.dirname(__file__), + "template", + self.args.format, + self.args.version, + "variables.tf", + ) + variables_file_path = os.path.join( + os.getcwd(), rules_dir, rule_name, rule_name.lower() + "_variables.tf" ) - variables_file_path = os.path.join(os.getcwd(), rules_dir, rule_name, rule_name.lower() + "_variables.tf") shutil.copy(variables_file_body, variables_file_path) print("Export completed.This will generate three .tf files.") @@ -2691,8 +3206,14 @@ def test_local(self): "python3.8-lib", "python3.9", "python3.9-lib", + "python3.10", + "python3.10-lib", ): - print("Skipping " + rule_name + " - Runtime not supported for local testing.") + print( + "Skipping " + + rule_name + + " - Runtime not supported for local testing." + ) continue print("Testing " + rule_name) @@ -2700,9 +3221,13 @@ def test_local(self): print("Looking for tests in " + test_dir) if args.verbose == True: - results = unittest.TextTestRunner(buffer=False, verbosity=2).run(self.__create_test_suite(test_dir)) + results = unittest.TextTestRunner(buffer=False, verbosity=2).run( + self.__create_test_suite(test_dir) + ) else: - results = unittest.TextTestRunner(buffer=True, verbosity=2).run(self.__create_test_suite(test_dir)) + results = unittest.TextTestRunner(buffer=True, verbosity=2).run( + self.__create_test_suite(test_dir) + ) print(results) @@ -2735,11 +3260,19 @@ def test_remote(self): # Generate test event from templates test_event = json.load( - open(os.path.join(path.dirname(__file__), "template", event_template_filename), "r"), strict=False + open( + os.path.join( + path.dirname(__file__), "template", event_template_filename + ), + "r", + ), + strict=False, ) my_invoking_event = json.loads(test_event["invokingEvent"]) my_invoking_event["configurationItem"] = my_ci - my_invoking_event["notificationCreationTime"] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z") + my_invoking_event[ + "notificationCreationTime" + ] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z") test_event["invokingEvent"] = json.dumps(my_invoking_event) test_event["ruleParameters"] = json.dumps(my_parameters) @@ -2792,11 +3325,15 @@ def logs(self): logGroupName=log_group_name, orderBy="LastEventTime", descending=True, - limit=int(self.args.number), # This is the worst-case scenario if there is only one event per stream + limit=int( + self.args.number + ), # This is the worst-case scenario if there is only one event per stream ) # Sadly we can't just use filter_log_events, since we don't know the timestamps yet and filter_log_events doesn't appear to support ordering. - my_events = self.__get_log_events(cw_logs, log_streams, int(self.args.number)) + my_events = self.__get_log_events( + cw_logs, log_streams, int(self.args.number) + ) latest_timestamp = 0 @@ -2841,8 +3378,12 @@ def logs(self): def rulesets(self): self.args = get_rulesets_parser().parse_args(self.args.command_args, self.args) - if self.args.subcommand in ["add", "remove"] and (not self.args.ruleset or not self.args.rulename): - print("You must specify a ruleset name and a rule for the `add` and `remove` commands.") + if self.args.subcommand in ["add", "remove"] and ( + not self.args.ruleset or not self.args.rulename + ): + print( + "You must specify a ruleset name and a rule for the `add` and `remove` commands." + ) return 1 if self.args.subcommand == "list": @@ -2855,7 +3396,9 @@ def rulesets(self): print("Unknown subcommand.") def create_terraform_template(self): - self.args = get_create_rule_template_parser().parse_args(self.args.command_args, self.args) + self.args = get_create_rule_template_parser().parse_args( + self.args.command_args, self.args + ) if self.args.rulesets: self.args.rulesets = self.args.rulesets.split(",") @@ -2877,7 +3420,9 @@ def create_terraform_template(self): print("CloudFormation template written to " + self.args.output_file) def create_rule_template(self): - self.args = get_create_rule_template_parser().parse_args(self.args.command_args, self.args) + self.args = get_create_rule_template_parser().parse_args( + self.args.command_args, self.args + ) if self.args.rulesets: self.args.rulesets = self.args.rulesets.split(",") @@ -2899,7 +3444,9 @@ def create_rule_template(self): parameters = {} parameters["LambdaAccountId"] = {} - parameters["LambdaAccountId"]["Description"] = "Account ID that contains Lambda functions for Config Rules." + parameters["LambdaAccountId"][ + "Description" + ] = "Account ID that contains Lambda functions for Config Rules." parameters["LambdaAccountId"]["Type"] = "String" parameters["LambdaAccountId"]["MinLength"] = "12" parameters["LambdaAccountId"]["MaxLength"] = "12" @@ -2916,17 +3463,29 @@ def create_rule_template(self): "RoleName": config_role_name, "Path": "/rdk/", "ManagedPolicyArns": [ - {"Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWS_ConfigRole"}, + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWS_ConfigRole" + }, {"Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess"}, ], "AssumeRolePolicyDocument": CONFIG_ROLE_ASSUME_ROLE_POLICY_DOCUMENT, - "Policies": [{"PolicyName": "DeliveryPermission", "PolicyDocument": CONFIG_ROLE_POLICY_DOCUMENT}], + "Policies": [ + { + "PolicyName": "DeliveryPermission", + "PolicyDocument": CONFIG_ROLE_POLICY_DOCUMENT, + } + ], } # Create Bucket for Config Data resources["ConfigBucket"] = { "Type": "AWS::S3::Bucket", - "Properties": {"BucketName": {"Fn::Sub": config_bucket_prefix + "-${AWS::AccountId}-${AWS::Region}"}}, + "Properties": { + "BucketName": { + "Fn::Sub": config_bucket_prefix + + "-${AWS::AccountId}-${AWS::Region}" + } + }, } # Create ConfigurationRecorder and DeliveryChannel @@ -2935,18 +3494,25 @@ def create_rule_template(self): "Properties": { "Name": "default", "RoleARN": {"Fn::GetAtt": ["ConfigRole", "Arn"]}, - "RecordingGroup": {"AllSupported": True, "IncludeGlobalResourceTypes": True}, + "RecordingGroup": { + "AllSupported": True, + "IncludeGlobalResourceTypes": True, + }, }, } if self.args.config_role_arn: - resources["ConfigurationRecorder"]["Properties"]["RoleARN"] = self.args.config_role_arn + resources["ConfigurationRecorder"]["Properties"][ + "RoleARN" + ] = self.args.config_role_arn resources["DeliveryChannel"] = { "Type": "AWS::Config::DeliveryChannel", "Properties": { "Name": "default", "S3BucketName": {"Ref": "ConfigBucket"}, - "ConfigSnapshotDeliveryProperties": {"DeliveryFrequency": "One_Hour"}, + "ConfigSnapshotDeliveryProperties": { + "DeliveryFrequency": "One_Hour" + }, }, } @@ -2958,7 +3524,10 @@ def create_rule_template(self): for input_param in input_params: cfn_param = {} cfn_param["Description"] = ( - "Pass-through to required Input Parameter " + input_param + " for Config Rule " + rule_name + "Pass-through to required Input Parameter " + + input_param + + " for Config Rule " + + rule_name ) if len(str(input_params[input_param]).strip()) == 0: default = "" @@ -2978,17 +3547,24 @@ def create_rule_template(self): for optional_param in optional_params: cfn_param = {} cfn_param["Description"] = ( - "Pass-through to optional Input Parameter " + optional_param + " for Config Rule " + rule_name + "Pass-through to optional Input Parameter " + + optional_param + + " for Config Rule " + + rule_name ) cfn_param["Default"] = optional_params[optional_param] cfn_param["Type"] = "String" - param_name = self.__get_alphanumeric_rule_name(rule_name) + optional_param + param_name = ( + self.__get_alphanumeric_rule_name(rule_name) + optional_param + ) parameters[param_name] = cfn_param optional_parameter_group["Parameters"].append(param_name) - conditions[param_name] = {"Fn::Not": [{"Fn::Equals": ["", {"Ref": param_name}]}]} + conditions[param_name] = { + "Fn::Not": [{"Fn::Equals": ["", {"Ref": param_name}]}] + } config_rule = {} config_rule["Type"] = "AWS::Config::ConfigRule" @@ -3013,7 +3589,10 @@ def create_rule_template(self): # Also add the appropriate event source. source["SourceDetails"].append( - {"EventSource": "aws.config", "MessageType": "ConfigurationItemChangeNotification"} + { + "EventSource": "aws.config", + "MessageType": "ConfigurationItemChangeNotification", + } ) if "SourcePeriodic" in params: source["SourceDetails"].append( @@ -3045,56 +3624,102 @@ def create_rule_template(self): if "InputParameters" in params: for required_param in json.loads(params["InputParameters"]): - cfn_param_name = self.__get_alphanumeric_rule_name(rule_name) + required_param - properties["InputParameters"][required_param] = {"Ref": cfn_param_name} + cfn_param_name = ( + self.__get_alphanumeric_rule_name(rule_name) + required_param + ) + properties["InputParameters"][required_param] = { + "Ref": cfn_param_name + } if "OptionalParameters" in params: for optional_param in json.loads(params["OptionalParameters"]): - cfn_param_name = self.__get_alphanumeric_rule_name(rule_name) + optional_param + cfn_param_name = ( + self.__get_alphanumeric_rule_name(rule_name) + optional_param + ) properties["InputParameters"][optional_param] = { - "Fn::If": [cfn_param_name, {"Ref": cfn_param_name}, {"Ref": "AWS::NoValue"}] + "Fn::If": [ + cfn_param_name, + {"Ref": cfn_param_name}, + {"Ref": "AWS::NoValue"}, + ] } config_rule["Properties"] = properties - config_rule_resource_name = self.__get_alphanumeric_rule_name(rule_name) + "ConfigRule" + config_rule_resource_name = ( + self.__get_alphanumeric_rule_name(rule_name) + "ConfigRule" + ) resources[config_rule_resource_name] = config_rule # If Remediation create the remediation section with potential links to the SSM Details if "Remediation" in params: - remediation = self.__create_remediation_cloudformation_block(params["Remediation"]) + remediation = self.__create_remediation_cloudformation_block( + params["Remediation"] + ) remediation["DependsOn"] = [config_rule_resource_name] if not self.args.rules_only: remediation["DependsOn"].append("ConfigRole") if "SSMAutomation" in params: - ssm_automation = self.__create_automation_cloudformation_block(params["SSMAutomation"], rule_name) + ssm_automation = self.__create_automation_cloudformation_block( + params["SSMAutomation"], rule_name + ) # AWS needs to build the SSM before the Config Rule - remediation["DependsOn"].append(self.__get_alphanumeric_rule_name(rule_name + "RemediationAction")) + remediation["DependsOn"].append( + self.__get_alphanumeric_rule_name( + rule_name + "RemediationAction" + ) + ) # Add JSON Reference to SSM Document { "Ref" : "MyEC2Instance" } remediation["Properties"]["TargetId"] = { - "Ref": self.__get_alphanumeric_rule_name(rule_name) + "RemediationAction" + "Ref": self.__get_alphanumeric_rule_name(rule_name) + + "RemediationAction" } if "IAM" in params["SSMAutomation"]: print("Lets Build IAM Role and Policy For the SSM Document") - ssm_iam_role, ssm_iam_policy = self.__create_automation_iam_cloudformation_block( + ( + ssm_iam_role, + ssm_iam_policy, + ) = self.__create_automation_iam_cloudformation_block( params["SSMAutomation"], rule_name ) - resources[self.__get_alphanumeric_rule_name(rule_name + "Role")] = ssm_iam_role - resources[self.__get_alphanumeric_rule_name(rule_name + "Policy")] = ssm_iam_policy - remediation["Properties"]["Parameters"]["AutomationAssumeRole"]["StaticValue"]["Values"] = [ - {"Fn::GetAtt": [self.__get_alphanumeric_rule_name(rule_name + "Role"), "Arn"]} + resources[ + self.__get_alphanumeric_rule_name(rule_name + "Role") + ] = ssm_iam_role + resources[ + self.__get_alphanumeric_rule_name(rule_name + "Policy") + ] = ssm_iam_policy + remediation["Properties"]["Parameters"]["AutomationAssumeRole"][ + "StaticValue" + ]["Values"] = [ + { + "Fn::GetAtt": [ + self.__get_alphanumeric_rule_name( + rule_name + "Role" + ), + "Arn", + ] + } ] # Override the placeholder to associate the SSM Document Role with newly crafted role - resources[self.__get_alphanumeric_rule_name(rule_name + "RemediationAction")] = ssm_automation - resources[self.__get_alphanumeric_rule_name(rule_name) + "Remediation"] = remediation + resources[ + self.__get_alphanumeric_rule_name( + rule_name + "RemediationAction" + ) + ] = ssm_automation + resources[ + self.__get_alphanumeric_rule_name(rule_name) + "Remediation" + ] = remediation if tags: tags_str = "" for tag in tags: - tags_str += "Key={},Value={} ".format(tag["Key"], tag["Value"]) - script_for_tag += "aws configservice tag-resource --resources-arn $(aws configservice describe-config-rules --config-rule-names {} --query 'ConfigRules[0].ConfigRuleArn' | tr -d '\"') --tags {} \n".format( - rule_name, tags_str + key = tag["Key"] + val = tag["Value"] + tags_str += f"Key={key},Value={val} " + script_for_tag += ( + "aws configservice tag-resource --resources-arn $(aws configservice describe-config-rules " + + f"--config-rule-names {rule_name} --query 'ConfigRules[0].ConfigRuleArn' | tr -d '\"') --tags {tags_str} \n" ) template["Resources"] = resources @@ -3103,7 +3728,10 @@ def create_rule_template(self): template["Metadata"] = { "AWS::CloudFormation::Interface": { "ParameterGroups": [ - {"Label": {"default": "Lambda Account ID"}, "Parameters": ["LambdaAccountId"]}, + { + "Label": {"default": "Lambda Account ID"}, + "Parameters": ["LambdaAccountId"], + }, required_parameter_group, optional_parameter_group, ], @@ -3120,7 +3748,9 @@ def create_rule_template(self): print("CloudFormation template written to " + self.args.output_file) if script_for_tag: - print("Found tags on config rules. Cloudformation do not support tagging config rule at the moment") + print( + "Found tags on config rules. Cloudformation do not support tagging config rule at the moment" + ) print("Generating script for config rules tags") script_for_tag = "#! /bin/bash \n" + script_for_tag if self.args.tag_config_rules_script: @@ -3129,10 +3759,14 @@ def create_rule_template(self): else: print("=========SCRIPT=========") print(script_for_tag) - print("you can use flag [--tag-config-rules-script ] to output the script") + print( + "you can use flag [--tag-config-rules-script ] to output the script" + ) def create_region_set(self): - self.args = get_create_region_set_parser().parse_args(self.args.command_args, self.args) + self.args = get_create_region_set_parser().parse_args( + self.args.command_args, self.args + ) output_file = self.args.output_file output_dict = { "default": ["us-east-1", "us-west-1", "eu-north-1", "ap-southeast-1"], @@ -3184,7 +3818,8 @@ def __list_rulesets(self): rules = [] for obj_name in os.listdir("."): - # print(obj_name) + if obj_name.startswith("."): + continue # Skip hidden items params_file_path = os.path.join(".", obj_name, parameter_file_name) if os.path.isfile(params_file_path): parameters_file = open(params_file_path, "r") @@ -3211,7 +3846,7 @@ def __get_template_dir(self): def __create_test_suite(self, test_dir): tests = [] - for (top, dirs, filenames) in os.walk(test_dir): + for top, dirs, filenames in os.walk(test_dir): for filename in fnmatch.filter(filenames, "*_test.py"): print(filename) sys.path.append(top) @@ -3233,39 +3868,39 @@ def __clean_rule_name(self, rule_name): return output def __create_java_rule(self): - src = os.path.join(path.dirname(__file__), "template", "runtime", "java8", "src") + src = os.path.join( + path.dirname(__file__), "template", "runtime", "java8", "src" + ) dst = os.path.join(os.getcwd(), rules_dir, self.args.rulename, "src") shutil.copytree(src, dst) - src = os.path.join(path.dirname(__file__), "template", "runtime", "java8", "jars") + src = os.path.join( + path.dirname(__file__), "template", "runtime", "java8", "jars" + ) dst = os.path.join(os.getcwd(), rules_dir, self.args.rulename, "jars") shutil.copytree(src, dst) - src = os.path.join(path.dirname(__file__), "template", "runtime", "java8", "build.gradle") + src = os.path.join( + path.dirname(__file__), "template", "runtime", "java8", "build.gradle" + ) dst = os.path.join(os.getcwd(), rules_dir, self.args.rulename, "build.gradle") shutil.copyfile(src, dst) - def __create_dotnet_rule(self): - runtime_path = os.path.join(path.dirname(__file__), "template", "runtime", self.args.runtime) - dst_path = os.path.join(os.getcwd(), rules_dir, self.args.rulename) - for obj in os.listdir(runtime_path): - src = os.path.join(runtime_path, obj) - dst = os.path.join(dst_path, obj) - if os.path.isfile(src): - shutil.copyfile(src, dst) - else: - shutil.copytree(src, dst) - def __print_log_event(self, event): - time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(event["timestamp"] / 1000)) + time_string = time.strftime( + "%Y-%m-%d %H:%M:%S", time.localtime(event["timestamp"] / 1000) + ) rows = 24 columns = 80 - try: - rows, columns = os.popen("stty size", "r").read().split() - except ValueError as e: - # This was probably being run in a headless test environment which had no stty. - print("Using default terminal rows and columns.") + if shutil.which("stty") is not None: + try: + rows, columns = os.popen("stty size", "r").read().split() + except Exception as e: + # This was probably being run in a headless test environment which had no stty. + print("Using default terminal rows and columns.") + else: + print("stty not present -- using default terminal rows and columns.") line_wrap = int(columns) - 22 message_lines = str(event["message"]).splitlines() @@ -3273,7 +3908,11 @@ def __print_log_event(self, event): for line in message_lines: line = line.replace("\t", " ") - formatted_lines.append("\n".join(line[i : i + line_wrap] for i in range(0, len(line), line_wrap))) + formatted_lines.append( + "\n".join( + line[i : i + line_wrap] for i in range(0, len(line), line_wrap) + ) + ) message_string = "\n".join(formatted_lines) message_string = message_string.replace("\n", "\n ") @@ -3326,7 +3965,11 @@ def __get_caller_identity_details(self, my_session): response = my_sts.get_caller_identity() arn_split = response["Arn"].split(":") - return {"account_id": response["Account"], "partition": arn_split[1], "region": arn_split[3]} + return { + "account_id": response["Account"], + "partition": arn_split[1], + "region": arn_split[3], + } def __get_stack_name_from_rule_name(self, rule_name): output = rule_name.replace("_", "") @@ -3341,22 +3984,33 @@ def __get_alphanumeric_rule_name(self, rule_name): def __get_rule_list_for_command(self, Command="deploy"): rule_names = [] if self.args.all: - d = "." for obj_name in os.listdir("."): obj_path = os.path.join(".", obj_name) if os.path.isdir(obj_path) and not obj_name == "rdk": for file_name in os.listdir(obj_path): if obj_name not in rule_names: - if os.path.exists(os.path.join(obj_path, "parameters.json")): + if os.path.exists( + os.path.join(obj_path, "parameters.json") + ): rule_names.append(obj_name) else: if file_name.split(".")[0] == obj_name: rule_names.append(obj_name) if os.path.exists( - os.path.join(obj_path, "src", "main", "java", "com", "rdk", "RuleCode.java") + os.path.join( + obj_path, + "src", + "main", + "java", + "com", + "rdk", + "RuleCode.java", + ) ): rule_names.append(obj_name) - if os.path.exists(os.path.join(obj_path, "RuleCode.cs")): + if os.path.exists( + os.path.join(obj_path, "RuleCode.cs") + ): rule_names.append(obj_name) elif self.args.rulesets: for obj_name in os.listdir("."): @@ -3376,7 +4030,10 @@ def __get_rule_list_for_command(self, Command="deploy"): if os.path.isdir(cleaned_rule_name): rule_names.append(cleaned_rule_name) else: - print('Invalid Option: Specify Rule Name or RuleSet. Run "rdk %s -h" for more info.' % (Command)) + print( + 'Invalid Option: Specify Rule Name or RuleSet. Run "rdk %s -h" for more info.' + % (Command) + ) sys.exit(1) if len(rule_names) == 0: @@ -3387,21 +4044,21 @@ def __get_rule_list_for_command(self, Command="deploy"): for name in rule_names: if len(name) > 128: print( - "Error: Found Rule with name over 128 characters: {} \n Recreate the Rule with a shorter name.".format( - name - ) + f"Error: Found Rule with name over 128 characters: {name} \n Recreate the Rule with a shorter name." ) sys.exit(1) return rule_names def __get_rule_parameters(self, rule_name): - params_file_path = os.path.join(os.getcwd(), rules_dir, rule_name, parameter_file_name) + params_file_path = os.path.join( + os.getcwd(), rules_dir, rule_name, parameter_file_name + ) try: parameters_file = open(params_file_path, "r") except IOError as e: - print("Failed to open parameters file for rule '{}'".format(rule_name)) + print(f"Failed to open parameters file for rule '{rule_name}'") print(e.message) sys.exit(1) @@ -3410,12 +4067,12 @@ def __get_rule_parameters(self, rule_name): try: my_json = json.load(parameters_file) except ValueError as ve: # includes simplejson.decoder.JSONDecodeError - print("Failed to decode JSON in parameters file for Rule {}".format(rule_name)) + print(f"Failed to decode JSON in parameters file for Rule {rule_name}") print(ve.message) parameters_file.close() sys.exit(1) except Exception as e: - print("Error loading parameters file for Rule {}".format(rule_name)) + print(f"Error loading parameters file for Rule {rule_name}") print(e.message) parameters_file.close() sys.exit(1) @@ -3436,7 +4093,9 @@ def __get_rule_parameters(self, rule_name): return my_json["Parameters"], my_tags def __parse_rule_args(self, is_required): - self.args = get_rule_parser(is_required, self.args.command).parse_args(self.args.command_args, self.args) + self.args = get_rule_parser(is_required, self.args.command).parse_args( + self.args.command_args, self.args + ) if self.args.rulename: if len(self.args.rulename) > 128: @@ -3448,7 +4107,10 @@ def __parse_rule_args(self, is_required): for resource_type in self.args.resource_types.split(","): if resource_type not in accepted_resource_types: resource_type_error = ( - resource_type_error + ' "' + resource_type + '" not found in list of accepted resource types.' + resource_type_error + + ' "' + + resource_type + + '" not found in list of accepted resource types.' ) if resource_type_error: print(resource_type_error) @@ -3459,8 +4121,14 @@ def __parse_rule_args(self, is_required): "Skip-Supported-Resource-Check Flag set (--skip-supported-resource-check), ignoring missing resource type error." ) - if is_required and not self.args.resource_types and not self.args.maximum_frequency: - print("You must specify either a resource type trigger or a maximum frequency.") + if ( + is_required + and not self.args.resource_types + and not self.args.maximum_frequency + ): + print( + "You must specify either a resource type trigger or a maximum frequency." + ) sys.exit(1) if self.args.input_parameters: @@ -3473,16 +4141,20 @@ def __parse_rule_args(self, is_required): if self.args.optional_parameters: try: - optional_params_dict = json.loads(self.args.optional_parameters, strict=False) + optional_params_dict = json.loads( + self.args.optional_parameters, strict=False + ) except Exception as e: - print("Failed to parse optional parameters.") + print(f"Failed to parse optional parameters. {repr(e)}") sys.exit(1) if self.args.rulesets: self.args.rulesets = self.args.rulesets.split(",") def __parse_test_args(self): - self.args = get_test_parser(self.args.command).parse_args(self.args.command_args, self.args) + self.args = get_test_parser(self.args.command).parse_args( + self.args.command_args, self.args + ) if self.args.all and self.args.rulename: print("You may specify either specific rules or --all, but not both.") @@ -3494,12 +4166,15 @@ def __parse_test_args(self): return self.args def __parse_deploy_args(self, ForceArgument=False): + self.args = get_deployment_parser(ForceArgument).parse_args( + self.args.command_args, self.args + ) - self.args = get_deployment_parser(ForceArgument).parse_args(self.args.command_args, self.args) - - ### Validate inputs ### + # Validate inputs # if self.args.stack_name and not self.args.functions_only: - print("--stack-name can only be specified when using the --functions-only feature.") + print( + "--stack-name can only be specified when using the --functions-only feature." + ) sys.exit(1) # Make sure we're not exceeding Layer limits @@ -3508,12 +4183,20 @@ def __parse_deploy_args(self, ForceArgument=False): if layer_count > 5: print("You may only specify 5 Lambda Layers.") sys.exit(1) - if self.args.rdklib_layer_arn or self.args.generated_lambda_layer and layer_count > 4: - print("Because you have selected a 'lib' runtime You may only specify 4 additional Lambda Layers.") + if ( + self.args.rdklib_layer_arn + or self.args.generated_lambda_layer + and layer_count > 4 + ): + print( + "Because you have selected a 'lib' runtime You may only specify 4 additional Lambda Layers." + ) sys.exit(1) # RDKLib version and RDKLib Layer ARN/Generated RDKLib Layer are mutually exclusive. - if "rdk_lib_version" in self.args and (self.args.rdklib_layer_arn or self.args.generated_lambda_layer): + if "rdk_lib_version" in self.args and ( + self.args.rdklib_layer_arn or self.args.generated_lambda_layer + ): print( "Specify EITHER an RDK Lib version to use the official release OR a specific Layer ARN to use a custom implementation." ) @@ -3521,7 +4204,9 @@ def __parse_deploy_args(self, ForceArgument=False): # RDKLib version and RDKLib Layer ARN/Generated RDKLib Layer are mutually exclusive. if self.args.rdklib_layer_arn and self.args.generated_lambda_layer: - print("Specify EITHER an RDK Lib Layer ARN OR the generated lambda layer flag.") + print( + "Specify EITHER an RDK Lib Layer ARN OR the generated lambda layer flag." + ) sys.exit(1) # Check rule names to make sure none are too long. This is needed to catch Rules created before length constraint was added. @@ -3529,9 +4214,7 @@ def __parse_deploy_args(self, ForceArgument=False): for name in self.args.rulename: if len(name) > 128: print( - "Error: Found Rule with name over 128 characters: {} \n Recreate the Rule with a shorter name.".format( - name - ) + f"Error: Found Rule with name over 128 characters: {name} \n Recreate the Rule with a shorter name." ) sys.exit(1) @@ -3542,12 +4225,15 @@ def __parse_deploy_args(self, ForceArgument=False): self.args.rulesets = self.args.rulesets.split(",") def __parse_deploy_organization_args(self, ForceArgument=False): + self.args = get_deployment_organization_parser(ForceArgument).parse_args( + self.args.command_args, self.args + ) - self.args = get_deployment_organization_parser(ForceArgument).parse_args(self.args.command_args, self.args) - - ### Validate inputs ### + # Validate inputs # if self.args.stack_name and not self.args.functions_only: - print("--stack-name can only be specified when using the --functions-only feature.") + print( + "--stack-name can only be specified when using the --functions-only feature." + ) sys.exit(1) # Make sure we're not exceeding Layer limits @@ -3557,7 +4243,9 @@ def __parse_deploy_organization_args(self, ForceArgument=False): print("You may only specify 5 Lambda Layers.") sys.exit(1) if self.args.rdklib_layer_arn and layer_count > 4: - print("Because you have selected a 'lib' runtime You may only specify 4 additional Lambda Layers.") + print( + "Because you have selected a 'lib' runtime You may only specify 4 additional Lambda Layers." + ) sys.exit(1) # RDKLib version and RDKLib Layer ARN are mutually exclusive. @@ -3572,9 +4260,7 @@ def __parse_deploy_organization_args(self, ForceArgument=False): for name in self.args.rulename: if len(name) > 128: print( - "Error: Found Rule with name over 128 characters: {} \n Recreate the Rule with a shorter name.".format( - name - ) + f"Error: Found Rule with name over 128 characters: {name} \n Recreate the Rule with a shorter name." ) sys.exit(1) @@ -3585,17 +4271,16 @@ def __parse_deploy_organization_args(self, ForceArgument=False): self.args.rulesets = self.args.rulesets.split(",") def __parse_export_args(self, ForceArgument=False): - - self.args = get_export_parser(ForceArgument).parse_args(self.args.command_args, self.args) + self.args = get_export_parser(ForceArgument).parse_args( + self.args.command_args, self.args + ) # Check rule names to make sure none are too long. This is needed to catch Rules created before length constraint was added. if self.args.rulename: for name in self.args.rulename: if len(name) > 128: print( - "Error: Found Rule with name over 128 characters: {} \n Recreate the Rule with a shorter name.".format( - name - ) + f"Error: Found Rule with name over 128 characters: {name} \n Recreate the Rule with a shorter name." ) sys.exit(1) @@ -3609,35 +4294,14 @@ def __package_function_code(self, rule_name, params): subprocess.call(command, cwd=working_dir) # set source as distribution zip - s3_src = os.path.join(os.getcwd(), rules_dir, rule_name, "build", "distributions", rule_name + ".zip") - elif params["SourceRuntime"] in ["dotnetcore1.0", "dotnetcore2.0"]: - print("Packaging " + rule_name) - working_dir = os.path.join(os.getcwd(), rules_dir, rule_name) - commands = [["dotnet", "restore"]] - - app_runtime = "netcoreapp1.0" - if params["SourceRuntime"] == "dotnetcore2.0": - app_runtime = "netcoreapp2.0" - - commands.append(["dotnet", "lambda", "package", "-c", "Release", "-f", app_runtime]) - - for command in commands: - subprocess.call(command, cwd=working_dir) - - # Remove old zip file if it already exists - package_file_dst = os.path.join(rule_name, rule_name + ".zip") - self.__delete_package_file(package_file_dst) - - # Create new package in temp directory, copy to rule directory - # This copy avoids the archiver trying to include the output zip in itself - s3_src_dir = os.path.join(os.getcwd(), rules_dir, rule_name, "bin", "Release", app_runtime, "publish") - tmp_src = shutil.make_archive( - os.path.join(tempfile.gettempdir(), rule_name + my_session.region_name), "zip", s3_src_dir + s3_src = os.path.join( + os.getcwd(), + rules_dir, + rule_name, + "build", + "distributions", + rule_name + ".zip", ) - if not (os.path.exists(package_file_dst)): - shutil.copy(tmp_src, package_file_dst) - s3_src = os.path.abspath(package_file_dst) - self.__delete_package_file(tmp_src) else: print("Zipping " + rule_name) @@ -3648,7 +4312,9 @@ def __package_function_code(self, rule_name, params): # zip rule code files and upload to s3 bucket s3_src_dir = os.path.join(os.getcwd(), rules_dir, rule_name) tmp_src = shutil.make_archive( - os.path.join(tempfile.gettempdir(), rule_name + my_session.region_name), "zip", s3_src_dir + os.path.join(tempfile.gettempdir(), rule_name + my_session.region_name), + "zip", + s3_src_dir, ) if not (os.path.exists(package_file_dst)): shutil.copy(tmp_src, package_file_dst) @@ -3687,7 +4353,9 @@ def __populate_params(self): if self.args.optional_parameters: # As above, but with the optional input parameters. try: - my_optional_params = json.loads(self.args.optional_parameters, strict=False) + my_optional_params = json.loads( + self.args.optional_parameters, strict=False + ) except Exception as e: print( "Error parsing optional input parameter JSON. Make sure your JSON keys and values are enclosed in properly escaped double quotes and your optional-parameters string is enclosed in single quotes." @@ -3699,7 +4367,7 @@ def __populate_params(self): # As above, but with the optional tag key value pairs. try: my_tags = json.loads(self.args.tags, strict=False) - except Exception as e: + except Exception: print( "Error parsing optional tags JSON. Make sure your JSON keys and values are enclosed in properly escaped double quotes and tags string is enclosed in single quotes." ) @@ -3719,12 +4387,14 @@ def __populate_params(self): ) and not self.args.remediation_action ): - print("Remediation Flags detected but no remediation action (--remediation-action) set") + print( + "Remediation Flags detected but no remediation action (--remediation-action) set" + ) if self.args.remediation_action: try: my_remediation = self.__generate_remediation_params() - except Exception as e: + except Exception: print("Error parsing remediation configuration.") # create config file and place in rule directory @@ -3732,7 +4402,7 @@ def __populate_params(self): "RuleName": self.args.rulename, "Description": self.args.rulename, "SourceRuntime": self.args.runtime, - #'CodeBucket': code_bucket_prefix + account_id, + # 'CodeBucket': code_bucket_prefix + account_id, "CodeKey": self.args.rulename + my_session.region_name + ".zip", "InputParameters": json.dumps(my_input_params), "OptionalParameters": json.dumps(my_optional_params), @@ -3771,7 +4441,9 @@ def __generate_remediation_params(self): ssm_controls = {} if self.args.remediation_concurrent_execution_percent: - ssm_controls["ConcurrentExecutionRatePercentage"] = self.args.remediation_concurrent_execution_percent + ssm_controls[ + "ConcurrentExecutionRatePercentage" + ] = self.args.remediation_concurrent_execution_percent if self.args.remediation_error_rate_percent: ssm_controls["ErrorPercentage"] = self.args.remediation_error_rate_percent @@ -3780,7 +4452,9 @@ def __generate_remediation_params(self): params["ExecutionControls"] = {"SsmControls": ssm_controls} if self.args.auto_remediation_retry_attempts: - params["MaximumAutomaticAttempts"] = self.args.auto_remediation_retry_attempts + params[ + "MaximumAutomaticAttempts" + ] = self.args.auto_remediation_retry_attempts if self.args.remediation_parameters: params["Parameters"] = json.loads(self.args.remediation_parameters) @@ -3801,7 +4475,9 @@ def __generate_remediation_params(self): def __write_params_file(self, rulename, parameters, tags): my_params = {"Version": "1.0", "Parameters": parameters, "Tags": tags} - params_file_path = os.path.join(os.getcwd(), rules_dir, rulename, parameter_file_name) + params_file_path = os.path.join( + os.getcwd(), rules_dir, rulename, parameter_file_name + ) parameters_file = open(params_file_path, "w") json.dump(my_params, parameters_file, indent=2) parameters_file.close() @@ -3814,8 +4490,8 @@ def __wait_for_cfn_stack(self, cfn_client, stackname): response = cfn_client.list_stacks() all_stacks = response["StackSummaries"] - while 'NextToken' in response: - response = cfn_client.list_stacks(NextToken=response['NextToken']) + while "NextToken" in response: + response = cfn_client.list_stacks(NextToken=response["NextToken"]) all_stacks += response["StackSummaries"] for stack in all_stacks: @@ -3833,26 +4509,44 @@ def __wait_for_cfn_stack(self, cfn_client, stackname): # If all stacks have been deleted, clearly we're done! if all_deleted: in_progress = False - print(f"[{my_session.region_name}]: CloudFormation stack operation complete.") + print( + f"[{my_session.region_name}]: CloudFormation stack operation complete." + ) continue else: if "FAILED" in active_stack["StackStatus"]: in_progress = False - print(f"[{my_session.region_name}]: CloudFormation stack operation Failed for " + stackname + ".") + print( + f"[{my_session.region_name}]: CloudFormation stack operation Failed for " + + stackname + + "." + ) if "StackStatusReason" in active_stack: - print(f"[{my_session.region_name}]: Reason: " + active_stack["StackStatusReason"]) + print( + f"[{my_session.region_name}]: Reason: " + + active_stack["StackStatusReason"] + ) elif active_stack["StackStatus"] == "ROLLBACK_COMPLETE": in_progress = False print( - f"[{my_session.region_name}]: CloudFormation stack operation Rolled Back for " + stackname + "." + f"[{my_session.region_name}]: CloudFormation stack operation Rolled Back for " + + stackname + + "." ) if "StackStatusReason" in active_stack: - print(f"[{my_session.region_name}]: Reason: " + active_stack["StackStatusReason"]) + print( + f"[{my_session.region_name}]: Reason: " + + active_stack["StackStatusReason"] + ) elif "COMPLETE" in active_stack["StackStatus"]: in_progress = False - print(f"[{my_session.region_name}]: CloudFormation stack operation complete.") + print( + f"[{my_session.region_name}]: CloudFormation stack operation complete." + ) else: - print(f"[{my_session.region_name}]: Waiting for CloudFormation stack operation to complete...") + print( + f"[{my_session.region_name}]: Waiting for CloudFormation stack operation to complete..." + ) time.sleep(5) def __get_handler(self, rule_name, params): @@ -3865,21 +4559,19 @@ def __get_handler(self, rule_name, params): "python3.8-lib", "python3.9", "python3.9-lib", - "nodejs6.10", - "nodejs8.10", + "python3.10", + "python3.10-lib", ]: return rule_name + ".lambda_handler" elif params["SourceRuntime"] in ["java8"]: return "com.rdk.RuleUtil::handler" - elif params["SourceRuntime"] in ["dotnetcore1.0", "dotnetcore2.0"]: - return "csharp7.0::Rdk.CustomConfigHandler::FunctionHandler" def __get_runtime_string(self, params): if params["SourceRuntime"] in [ - "python3.6-managed", "python3.7-lib", "python3.8-lib", "python3.9-lib", + "python3.10-lib", ]: runtime = params["SourceRuntime"].split("-") return runtime[0] @@ -3896,9 +4588,13 @@ def __get_test_CIs(self, rulename): test_ci_list.append(my_test_ci.get_json()) else: # Check to see if there is a test_ci.json file in the Rule directory - tests_path = os.path.join(os.getcwd(), rules_dir, rulename, test_ci_filename) + tests_path = os.path.join( + os.getcwd(), rules_dir, rulename, test_ci_filename + ) if os.path.exists(tests_path): - print("\tTesting with CI's provided in test_ci.json file. NOT YET IMPLEMENTED") # TODO + print( + "\tTesting with CI's provided in test_ci.json file. NOT YET IMPLEMENTED" + ) # TODO # test_ci_list self._load_cis_from_file(tests_path) else: print("\tTesting with generic CI for configured Resource Type(s)") @@ -3916,7 +4612,8 @@ def __get_lambda_arn_for_stack(self, stack_name): my_cfn = my_session.client("cloudformation") - # Since CFN won't detect changes to the lambda code stored in S3 as a reason to update the stack, we need to manually update the code reference in Lambda once the CFN has run. + # Since CFN won't detect changes to the lambda code stored in S3 as a reason to update the stack, + # we need to manually update the code reference in Lambda once the CFN has run. self.__wait_for_cfn_stack(my_cfn, stack_name) # Lambda function is an output of the stack. @@ -3928,7 +4625,9 @@ def __get_lambda_arn_for_stack(self, stack_name): my_lambda_arn = output["OutputValue"] if my_lambda_arn == "NOTFOUND": - print(f"[{my_session.region_name}]: Could not read CloudFormation stack output to find Lambda function.") + print( + f"[{my_session.region_name}]: Could not read CloudFormation stack output to find Lambda function." + ) sys.exit(1) return my_lambda_arn @@ -3938,27 +4637,27 @@ def __get_lambda_name(self, rule_name, params): lambda_name = params["CustomLambdaName"] if len(lambda_name) > 64: print( - "Error: Found Rule's Lambda function with name over 64 characters: {} \n Recreate the lambda name with a shorter name.".format( - lambda_name - ) + f"Error: Found Rule's Lambda function with name over 64 characters: {lambda_name}." + + "\nRecreate the lambda name with a shorter name." ) sys.exit(1) return lambda_name else: - lambda_name = "RDK-Rule-Function-" + self.__get_stack_name_from_rule_name(rule_name) + lambda_name = "RDK-Rule-Function-" + self.__get_stack_name_from_rule_name( + rule_name + ) if len(lambda_name) > 64: print( - "Error: Found Rule's Lambda function with name over 64 characters: {} \n Recreate the rule with a shorter name or with CustomLambdaName attribute in parameter.json. If you are using 'rdk create', you can add '--custom-lambda-name ' to create your RDK rules".format( - lambda_name - ) + f"Error: Found Rule's Lambda function with name over 64 characters: {lambda_name}." + + "\nRecreate the rule with a shorter name or with CustomLambdaName attribute in parameter.json." + + "\nIf you are using 'rdk create', you can add '--custom-lambda-name ' to create your RDK rules" ) sys.exit(1) return lambda_name def __get_lambda_arn_for_rule(self, rule_name, partition, region, account, params): - return "arn:{}:lambda:{}:{}:function:{}".format( - partition, region, account, self.__get_lambda_name(rule_name, params) - ) + lambda_name = self.__get_lambda_name(rule_name, params) + return f"arn:{partition}:lambda:{region}:{account}:function:{lambda_name}" def __delete_package_file(self, file): try: @@ -3966,7 +4665,9 @@ def __delete_package_file(self, file): except OSError: pass - def __upload_function_code(self, rule_name, params, account_id, my_session, code_bucket_name): + def __upload_function_code( + self, rule_name, params, account_id, my_session, code_bucket_name + ): if params["SourceRuntime"] == "java8": # Do java build and package. print(f"[{my_session.region_name}]: Running Gradle Build for " + rule_name) @@ -3976,7 +4677,12 @@ def __upload_function_code(self, rule_name, params, account_id, my_session, code # set source as distribution zip s3_src = os.path.join( - os.getcwd(), rules_dir, rule_name, "build", "distributions", rule_name + my_session.region_name + ".zip" + os.getcwd(), + rules_dir, + rule_name, + "build", + "distributions", + rule_name + my_session.region_name + ".zip", ) s3_dst = "/".join((rule_name, rule_name + ".zip")) @@ -3986,41 +4692,6 @@ def __upload_function_code(self, rule_name, params, account_id, my_session, code my_s3.meta.client.upload_file(s3_src, code_bucket_name, s3_dst) print(f"[{my_session.region_name}]: Upload complete.") - elif params["SourceRuntime"] in ["dotnetcore1.0", "dotnetcore2.0"]: - print("Packaging " + rule_name) - working_dir = os.path.join(os.getcwd(), rules_dir, rule_name) - commands = [["dotnet", "restore"]] - - app_runtime = "netcoreapp1.0" - if params["SourceRuntime"] == "dotnetcore2.0": - app_runtime = "netcoreapp2.0" - - commands.append(["dotnet", "lambda", "package", "-c", "Release", "-f", app_runtime]) - - for command in commands: - subprocess.call(command, cwd=working_dir) - - # Remove old zip file if it already exists - package_file_dst = os.path.join(rule_name, rule_name + ".zip") - self.__delete_package_file(package_file_dst) - - # Create new package in temp directory, copy to rule directory - # This copy avoids the archiver trying to include the output zip in itself - s3_src_dir = os.path.join(os.getcwd(), rules_dir, rule_name, "bin", "Release", app_runtime, "publish") - tmp_src = shutil.make_archive( - os.path.join(tempfile.gettempdir(), rule_name + my_session.region_name), "zip", s3_src_dir - ) - s3_dst = "/".join((rule_name, rule_name + ".zip")) - - my_s3 = my_session.resource("s3") - - print(f"[{my_session.region_name}]: Uploading " + rule_name) - my_s3.meta.client.upload_file(tmp_src, code_bucket_name, s3_dst) - print(f"[{my_session.region_name}]: Upload complete.") - if not (os.path.exists(package_file_dst)): - shutil.copy(tmp_src, package_file_dst) - self.__delete_package_file(tmp_src) - else: print(f"[{my_session.region_name}]: Zipping " + rule_name) # Remove old zip file if it already exists @@ -4031,7 +4702,9 @@ def __upload_function_code(self, rule_name, params, account_id, my_session, code s3_src_dir = os.path.join(os.getcwd(), rules_dir, rule_name) tmp_src = shutil.make_archive( - os.path.join(tempfile.gettempdir(), rule_name + my_session.region_name), "zip", s3_src_dir + os.path.join(tempfile.gettempdir(), rule_name + my_session.region_name), + "zip", + s3_src_dir, ) s3_dst = "/".join((rule_name, rule_name + ".zip")) @@ -4058,7 +4731,6 @@ def __create_remediation_cloudformation_block(self, remediation_config): def __create_automation_cloudformation_block(self, ssm_automation, rule_name): print("Generate SSM Resources") - current_working_direcoty = os.getcwd() ssm_json_dir = os.path.join(os.getcwd(), ssm_automation["Document"]) print("Reading SSM JSON From -> " + ssm_json_dir) # params_file_path = os.path.join(os.getcwd(), rules_dir, rulename, parameter_file_name) @@ -4066,19 +4738,28 @@ def __create_automation_cloudformation_block(self, ssm_automation, rule_name): ssm_automation_json = json.loads(ssm_automation_content) ssm_automation_config = { "Type": "AWS::SSM::Document", - "Properties": {"DocumentType": "Automation", "Content": ssm_automation_json}, + "Properties": { + "DocumentType": "Automation", + "Content": ssm_automation_json, + }, } return ssm_automation_config def __create_automation_iam_cloudformation_block(self, ssm_automation, rule_name): - - print("Generate IAM Role for SSM Document with these actions", str(ssm_automation["IAM"])) + print( + "Generate IAM Role for SSM Document with these actions", + str(ssm_automation["IAM"]), + ) assume_role_template = { "Version": "2012-10-17", "Statement": [ - {"Effect": "Allow", "Principal": {"Service": "ssm.amazonaws.com"}, "Action": "sts:AssumeRole"} + { + "Effect": "Allow", + "Principal": {"Service": "ssm.amazonaws.com"}, + "Action": "sts:AssumeRole", + } ], } @@ -4086,7 +4767,8 @@ def __create_automation_iam_cloudformation_block(self, ssm_automation, rule_name ssm_automation_iam_role = { "Type": "AWS::IAM::Role", "Properties": { - "Description": "IAM Role to Support Config Remediation for " + rule_name, + "Description": "IAM Role to Support Config Remediation for " + + rule_name, "Path": "/rdk-remediation-role/", # "RoleName": {"Fn::Sub": "" + rule_name + "-Remediation-Role-${AWS::Region}"}, "AssumeRolePolicyDocument": assume_role_template, @@ -4097,11 +4779,21 @@ def __create_automation_iam_cloudformation_block(self, ssm_automation, rule_name "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { - "Statement": [{"Action": ssm_automation["IAM"], "Effect": "Allow", "Resource": "*"}], + "Statement": [ + { + "Action": ssm_automation["IAM"], + "Effect": "Allow", + "Resource": "*", + } + ], "Version": "2012-10-17", }, - "PolicyName": {"Fn::Sub": "" + rule_name + "-Remediation-Policy-${AWS::Region}"}, - "Roles": [{"Ref": self.__get_alphanumeric_rule_name(rule_name + "Role")}], + "PolicyName": { + "Fn::Sub": "" + rule_name + "-Remediation-Policy-${AWS::Region}" + }, + "Roles": [ + {"Ref": self.__get_alphanumeric_rule_name(rule_name + "Role")} + ], }, } @@ -4119,7 +4811,9 @@ def __create_function_cloudformation_template(self): parameters = {} parameters["SourceBucket"] = {} - parameters["SourceBucket"]["Description"] = "Name of the S3 bucket that you have stored the rule zip files in." + parameters["SourceBucket"][ + "Description" + ] = "Name of the S3 bucket that you have stored the rule zip files in." parameters["SourceBucket"]["Type"] = "String" parameters["SourceBucket"]["MinLength"] = "1" parameters["SourceBucket"]["MaxLength"] = "255" @@ -4134,10 +4828,16 @@ def __create_function_cloudformation_template(self): partition = identity_details["partition"] lambdaRoleArn = "" if self.args.lambda_role_arn: - print(f"[{my_session.region_name}]: Existing IAM Role provided: " + self.args.lambda_role_arn) + print( + f"[{my_session.region_name}]: Existing IAM Role provided: " + + self.args.lambda_role_arn + ) lambdaRoleArn = self.args.lambda_role_arn elif self.args.lambda_role_name: - print(f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + self.args.lambda_role_name) + print( + f"[{my_session.region_name}]: Building IAM Role ARN from Name: " + + self.args.lambda_role_name + ) arn = f"arn:{partition}:iam::{account_id}:role/{self.args.lambda_role_name}" lambdaRoleArn = arn else: @@ -4158,12 +4858,6 @@ def __create_function_cloudformation_template(self): ], } lambda_policy_statements = [ - { - "Sid": "1", - "Action": ["s3:GetObject"], - "Effect": "Allow", - "Resource": {"Fn::Sub": "arn:${AWS::Partition}:s3:::${SourceBucket}/*"}, - }, { "Sid": "2", "Action": [ @@ -4175,9 +4869,24 @@ def __create_function_cloudformation_template(self): "Effect": "Allow", "Resource": "*", }, - {"Sid": "3", "Action": ["config:PutEvaluations"], "Effect": "Allow", "Resource": "*"}, - {"Sid": "4", "Action": ["iam:List*", "iam:Describe*", "iam:Get*"], "Effect": "Allow", "Resource": "*"}, - {"Sid": "5", "Action": ["sts:AssumeRole"], "Effect": "Allow", "Resource": "*"}, + { + "Sid": "3", + "Action": ["config:PutEvaluations"], + "Effect": "Allow", + "Resource": "*", + }, + { + "Sid": "4", + "Action": ["iam:List*", "iam:Get*"], + "Effect": "Allow", + "Resource": "*", + }, + { + "Sid": "5", + "Action": ["sts:AssumeRole"], + "Effect": "Allow", + "Resource": "*", + }, ] if self.args.lambda_subnets and self.args.lambda_security_groups: vpc_policy = { @@ -4194,7 +4903,10 @@ def __create_function_cloudformation_template(self): lambda_role["Properties"]["Policies"] = [ { "PolicyName": "ConfigRulePolicy", - "PolicyDocument": {"Version": "2012-10-17", "Statement": lambda_policy_statements}, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": lambda_policy_statements, + }, } ] lambda_role["Properties"]["ManagedPolicyArns"] = [ @@ -4215,7 +4927,10 @@ def __create_function_cloudformation_template(self): lambda_function["Type"] = "AWS::Lambda::Function" properties = {} properties["FunctionName"] = self.__get_lambda_name(rule_name, params) - properties["Code"] = {"S3Bucket": {"Ref": "SourceBucket"}, "S3Key": rule_name + "/" + rule_name + ".zip"} + properties["Code"] = { + "S3Bucket": {"Ref": "SourceBucket"}, + "S3Key": rule_name + "/" + rule_name + ".zip", + } properties["Description"] = "Function for AWS Config Rule " + rule_name properties["Handler"] = self.__get_handler(rule_name, params) properties["MemorySize"] = "256" @@ -4248,7 +4963,9 @@ def __create_function_cloudformation_template(self): lambda_permissions["Type"] = "AWS::Lambda::Permission" lambda_permissions["DependsOn"] = alphanum_rule_name + "LambdaFunction" lambda_permissions["Properties"] = { - "FunctionName": {"Fn::GetAtt": [alphanum_rule_name + "LambdaFunction", "Arn"]}, + "FunctionName": { + "Fn::GetAtt": [alphanum_rule_name + "LambdaFunction", "Arn"] + }, "Action": "lambda:InvokeFunction", "Principal": "config.amazonaws.com", } @@ -4260,15 +4977,25 @@ def __create_function_cloudformation_template(self): def __tag_config_rule(self, rule_name, cfn_tags, my_session): config_client = my_session.client("config") - config_arn = config_client.describe_config_rules(ConfigRuleNames=[rule_name])["ConfigRules"][0]["ConfigRuleArn"] + config_arn = config_client.describe_config_rules(ConfigRuleNames=[rule_name])[ + "ConfigRules" + ][0]["ConfigRuleArn"] response = config_client.tag_resource(ResourceArn=config_arn, Tags=cfn_tags) return response def __get_lambda_layers(self, my_session, args, params): layers = [] if "SourceRuntime" in params: - if params["SourceRuntime"] in ["python3.7-lib", "python3.8-lib", "python3.9-lib"]: - if hasattr(args, "generated_lambda_layer") and args.generated_lambda_layer: + if params["SourceRuntime"] in [ + "python3.7-lib", + "python3.8-lib", + "python3.9-lib", + "python3.10-lib", + ]: + if ( + hasattr(args, "generated_lambda_layer") + and args.generated_lambda_layer + ): lambda_layer_version = self.__get_existing_lambda_layer( my_session, layer_name=args.custom_layer_name ) @@ -4276,7 +5003,9 @@ def __get_lambda_layers(self, my_session, args, params): print( f"{my_session.region_name} generated-lambda-layer flag received, but layer [{args.custom_layer_name}] not found in {my_session.region_name}. Creating one now" ) - self.__create_new_lambda_layer(my_session, layer_name=args.custom_layer_name) + self.__create_new_lambda_layer( + my_session, layer_name=args.custom_layer_name + ) lambda_layer_version = self.__get_existing_lambda_layer( my_session, layer_name=args.custom_layer_name ) @@ -4285,7 +5014,9 @@ def __get_lambda_layers(self, my_session, args, params): layers.append(args.rdklib_layer_arn) else: rdk_lib_version = RDKLIB_LAYER_VERSION[my_session.region_name] - rdklib_arn = RDKLIB_ARN_STRING.format(region=my_session.region_name, version=rdk_lib_version) + rdklib_arn = RDKLIB_ARN_STRING.format( + region=my_session.region_name, version=rdk_lib_version + ) layers.append(rdklib_arn) return layers @@ -4300,10 +5031,11 @@ def __get_existing_lambda_layer(self, my_session, layer_name="rdklib-layer"): return None def __create_new_lambda_layer(self, my_session, layer_name="rdklib-layer"): - successful_return = None if layer_name == "rdklib-layer": - successful_return = self.__create_new_lambda_layer_serverless_repo(my_session) + successful_return = self.__create_new_lambda_layer_serverless_repo( + my_session + ) # If that doesn't work, create it locally and upload - SAR doesn't support the custom layer name if layer_name != "rdklib-layer" or not successful_return: @@ -4334,7 +5066,9 @@ def __create_new_lambda_layer_serverless_repo(self, my_session): change_set_arn = sar_client.create_cloud_formation_change_set( ApplicationId=RDKLIB_LAYER_SAR_ID, StackName="rdklib" )["ChangeSetId"] - print(f"[{my_session.region_name}]: Creating change set to deploy rdklib-layer") + print( + f"[{my_session.region_name}]: Creating change set to deploy rdklib-layer" + ) code = self.__check_on_change_set(cfn_client, change_set_arn) if code == 1: print( @@ -4342,9 +5076,13 @@ def __create_new_lambda_layer_serverless_repo(self, my_session): ) return 1 if code == -1: - print(f"[{my_session.region_name}]: Error creating change set, attempting to use manual deployment") + print( + f"[{my_session.region_name}]: Error creating change set, attempting to use manual deployment" + ) raise ClientError() - print(f"[{my_session.region_name}]: Executing change set to deploy rdklib-layer") + print( + f"[{my_session.region_name}]: Executing change set to deploy rdklib-layer" + ) cfn_client.execute_change_set(ChangeSetName=change_set_arn) waiter = cfn_client.get_waiter(f"stack_{create_type}_complete") waiter.wait(StackName="serverlessrepo-rdklib") @@ -4358,7 +5096,9 @@ def __create_new_lambda_layer_locally(self, my_session, layer_name="rdklib-layer region = my_session.region_name print(f"[{region}]: Creating new {layer_name}") folder_name = "lib" + str(uuid.uuid4()) - shell_command = f"pip3 install --target python boto3 botocore rdk rdklib future mock" + shell_command = ( + "pip3 install --target python boto3 botocore rdk rdklib future mock" + ) print(f"[{region}]: Installing Packages to {folder_name}/python") try: @@ -4367,7 +5107,7 @@ def __create_new_lambda_layer_locally(self, my_session, layer_name="rdklib-layer print(e) sys.exit(1) os.chdir(folder_name) - ret = subprocess.run(shell_command, capture_output=True, shell=True) + _ = subprocess.run(shell_command, capture_output=True, shell=True) print(f"[{region}]: Creating rdk_lib_layer.zip") shutil.make_archive(f"rdk_lib_layer", "zip", ".", "python") @@ -4378,12 +5118,17 @@ def __create_new_lambda_layer_locally(self, my_session, layer_name="rdklib-layer print(f"[{region}]: Creating temporary S3 Bucket") bucket_name = "rdkliblayertemp" + str(uuid.uuid4()) if region != "us-east-1": - s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={"LocationConstraint": region}) + s3_client.create_bucket( + Bucket=bucket_name, + CreateBucketConfiguration={"LocationConstraint": region}, + ) if region == "us-east-1": s3_client.create_bucket(Bucket=bucket_name) print(f"[{region}]: Uploading rdk_lib_layer.zip to S3") - s3_resource.Bucket(bucket_name).upload_file(f"{folder_name}/rdk_lib_layer.zip", layer_name) + s3_resource.Bucket(bucket_name).upload_file( + f"{folder_name}/rdk_lib_layer.zip", layer_name + ) lambda_client = my_session.client("lambda") @@ -4391,7 +5136,7 @@ def __create_new_lambda_layer_locally(self, my_session, layer_name="rdklib-layer lambda_client.publish_layer_version( LayerName=layer_name, Content={"S3Bucket": bucket_name, "S3Key": layer_name}, - CompatibleRuntimes=["python3.7", "python3.8", "python3.9"], + CompatibleRuntimes=["python3.7", "python3.8", "python3.9", "python3.10"], ) print(f"[{region}]: Deleting temporary S3 Bucket") @@ -4424,14 +5169,20 @@ def __init__(self, ci_type): ci_file = ci_type.replace("::", "_") + ".json" try: self.ci_json = json.load( - open(os.path.join(path.dirname(__file__), "template", example_ci_dir, ci_file), "r") + open( + os.path.join( + path.dirname(__file__), "template", example_ci_dir, ci_file + ), + "r", + ) ) except FileNotFoundError: + resource_url = "https://github.com/awslabs/aws-config-resource-schema/blob/master/config/properties/resource-types/" print( "No sample CI found for " + ci_type + ", even though it appears to be a supported CI. Please log an issue at https://github.com/awslabs/aws-config-rdk." - + "\nLook here: https://github.com/awslabs/aws-config-resource-schema/blob/master/config/properties/resource-types/ for additional info" + + f"\nLook here: {resource_url} for additional info" ) exit(1) diff --git a/rdk/template/configRule.json b/rdk/template/configRule.json index e8fe4fa9..43ab74a5 100644 --- a/rdk/template/configRule.json +++ b/rdk/template/configRule.json @@ -221,14 +221,6 @@ "PolicyDocument": { "Version": "2012-10-17", "Statement": [ - { - "Sid": "1", - "Action": [ - "s3:GetObject" - ], - "Effect": "Allow", - "Resource": { "Fn::Sub": "arn:${AWS::Partition}:s3:::${SourceBucket}/${SourcePath}" } - }, { "Sid": "2", "Action": [ @@ -252,7 +244,6 @@ "Sid": "4", "Action": [ "iam:List*", - "iam:Describe*", "iam:Get*" ], "Effect": "Allow", diff --git a/rdk/template/configRuleOrganization.json b/rdk/template/configRuleOrganization.json index 59d1c58f..52dd506e 100644 --- a/rdk/template/configRuleOrganization.json +++ b/rdk/template/configRuleOrganization.json @@ -199,14 +199,6 @@ "PolicyDocument": { "Version": "2012-10-17", "Statement": [ - { - "Sid": "1", - "Action": [ - "s3:GetObject" - ], - "Effect": "Allow", - "Resource": { "Fn::Sub": "arn:${AWS::Partition}:s3:::${SourceBucket}/${SourcePath}" } - }, { "Sid": "2", "Action": [ @@ -230,7 +222,6 @@ "Sid": "4", "Action": [ "iam:List*", - "iam:Describe*", "iam:Get*" ], "Effect": "Allow", diff --git a/rdk/template/example_ci/AWS_R53_HostedZone.json b/rdk/template/example_ci/AWS_R53_HostedZone.json new file mode 100644 index 00000000..e93a324a --- /dev/null +++ b/rdk/template/example_ci/AWS_R53_HostedZone.json @@ -0,0 +1,39 @@ +{ + "version": "1.3", + "accountId": "123456789012", + "configurationItemCaptureTime": "2023-05-01T18:00:07.672Z", + "configurationItemStatus": "ResourceDiscovered", + "configurationStateId": "1682964007672", + "configurationItemMD5Hash": "", + "arn": "arn:aws:route53:::hostedzone/Z017455410COBZEF0ABCD", + "resourceType": "AWS::Route53::HostedZone", + "resourceId": "Z017455410COBZEF0ABCD", + "resourceName": "testdomain.lab.", + "awsRegion": "us-east-1", + "availabilityZone": "Regional", + "tags": {}, + "relatedEvents": [], + "relationships": [], + "configuration": { + "Id": "Z017455410COBZEF0ABCD", + "HostedZoneConfig": { + "Comment": "This is a test domain" + }, + "Name": "testdomain.lab.", + "NameServers": [ + "ns-1965.awsdns-53.co.uk", + "ns-944.awsdns-54.net", + "ns-1144.awsdns-15.org", + "ns-430.awsdns-53.com" + ], + "VPCs": [], + "HostedZoneTags": [ + { + "Key": "cost_center", + "Value": "payroll" + } + ] + }, + "supplementaryConfiguration": {}, + "resourceTransitionStatus": "None" + } \ No newline at end of file diff --git a/rdk/template/example_ci/AWS_S3_AccountPublicAccessBlock.json b/rdk/template/example_ci/AWS_S3_AccountPublicAccessBlock.json new file mode 100644 index 00000000..23b6d759 --- /dev/null +++ b/rdk/template/example_ci/AWS_S3_AccountPublicAccessBlock.json @@ -0,0 +1,23 @@ +{ + "version": "1.3", + "accountId": "123456789012", + "configurationItemCaptureTime": "2022-05-20T15:53:57.732Z", + "configurationItemStatus": "ResourceDiscovered", + "configurationStateId": "1653062037732", + "configurationItemMD5Hash": "", + "resourceType": "AWS::S3::AccountPublicAccessBlock", + "resourceId": "123456789012", + "awsRegion": "us-east-1", + "availabilityZone": "Not Applicable", + "tags": {}, + "relatedEvents": [], + "relationships": [], + "configuration": { + "blockPublicAcls": true, + "ignorePublicAcls": true, + "blockPublicPolicy": true, + "restrictPublicBuckets": true + }, + "supplementaryConfiguration": {}, + "resourceTransitionStatus": "None" + } \ No newline at end of file diff --git a/rdk/template/example_ci/AWS_SSM_ManagedInstanceInventory.json b/rdk/template/example_ci/AWS_SSM_ManagedInstanceInventory.json index 2846342f..109534b5 100644 --- a/rdk/template/example_ci/AWS_SSM_ManagedInstanceInventory.json +++ b/rdk/template/example_ci/AWS_SSM_ManagedInstanceInventory.json @@ -48,1772 +48,1519 @@ "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2894856", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Friday, June 20, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2896496", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 18, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2919355", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Tuesday, March 18, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2919442", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Saturday, May 17, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2920189", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Tuesday, January 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB2934520", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, July 10, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2938066", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 18, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2938772", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Tuesday, March 18, 2014 12:00:00 AM", "Description": "Hotfix", "HotFixID": "KB2949621", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Saturday, May 17, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2954879", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Saturday, May 17, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2955164", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, July 10, 2014 12:00:00 AM", "Description": "Hotfix", "HotFixID": "KB2959626", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Friday, June 20, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2962409", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, January 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB2962806", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, May 17, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2965500", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Thursday, July 10, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2967917", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Friday, June 20, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2969339", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, July 10, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2971203", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, July 10, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2973351", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Friday, June 20, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2973448", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, July 10, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2975061", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2975719", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2976627", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2977765", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2978041", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, November 18, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2978126", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2984006", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB2987107", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2989647", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, December 9, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2989930", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2993100", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2995004", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2995388", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Hotfix", "HotFixID": "KB2996799", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, October 15, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB2998174", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB2999226", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3000483", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, November 18, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB3000850", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, November 18, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3003057", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, February 10, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3004361", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3004365", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Hotfix", "HotFixID": "KB3004545", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, December 9, 2014 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3008923", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, December 9, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB3012199", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3012702", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3013172", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, December 9, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB3013769", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3013791", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, December 9, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB3013816", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, November 18, 2014 12:00:00 AM", "Description": "Update", "HotFixID": "KB3014442", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, January 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3019978", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, February 10, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3020338", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3021910", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, February 10, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3021952", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3022345", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, January 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3022777", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3023222", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, January 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3023266", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3024751", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3024755", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3029603", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3030377", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3030947", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3032359", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3032663", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3033446", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3035126", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 10, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3036612", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3037579", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3037924", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3038002", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3038314", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Hotfix", "HotFixID": "KB3038701", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3041857", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3042085", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3042553", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3044374", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3044673", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3045634", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3045685", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3045717", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3045719", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3045755", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3045992", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, April 15, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3045999", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3046017", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Hotfix", "HotFixID": "KB3046737", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3048043", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3049563", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3054169", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3054203", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3054256", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3054464", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3055323", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3055343", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, May 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3055642", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3059316", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3059317", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3060681", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3060793", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3061512", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3063843", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3064209", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3068708", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3071756", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3074228", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3074548", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3075220", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3075853", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3077715", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, August 13, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3078071", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3078405", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3078676", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3080042", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3080149", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3082089", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3083325", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3083711", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3083992", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3084135", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3084905", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3086255", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3087038", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3087041", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3087137", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3091297", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, November 12, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3092601", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3092627", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3093983", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3094486", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3095701", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, October 22, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3096433", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, November 12, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3097997", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, November 12, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3098779", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3098785", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3099834", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3100473", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, November 12, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3100773", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3100919", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3100956", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3102429", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3102467", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, November 12, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3102812", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3103616", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3103696", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3103709", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3104002", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3109094", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3109103", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3109976", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, January 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3110329", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3112148", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, December 9, 2015 12:00:00 AM", "Description": "Update", "HotFixID": "KB3112336", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3115224", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3118401", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3121255", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3121261", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, January 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3121461", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, January 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3121918", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3122654", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3122660", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3123242", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3123245", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, January 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3123479", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, January 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3124275", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3125424", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3126033", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3126434", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3126587", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3126593", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3127226", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3127231", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3128650", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3133043", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3133681", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3133690", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3133924", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3134179", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3134242", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3134814", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3134815", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3135449", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3135456", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3135998", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3137061", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3137725", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3137728", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3138602", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3138615", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3139164", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3139398", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3139914", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, March 8, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3139929", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3140219", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3140234", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, February 11, 2016 12:00:00 AM", "Description": "Hotfix", "HotFixID": "KB3141092", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3142036", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3145384", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3145432", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3146604", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3146723", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3146751", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3146963", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3147071", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3148198", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3148851", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, April 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3149090", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3149157", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3153704", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3154070", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3155784", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3156016", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3156017", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3156019", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Thursday, May 12, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3156059", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3156418", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3159398", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3160005", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3161561", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3161949", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3161958", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3162343", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3162835", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3164024", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3164033", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3164035", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, June 15, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3164294", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3167679", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3169704", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3170377", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3170455", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3172614", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3172727", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3172729", - "InstalledBy": "WIN-61TNU83K1V4 -Administrator" + "InstalledBy": "WIN-61TNU83K1V4\\Administrator" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3173424", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3174644", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3175024", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3175443", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3175887", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3177108", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3177186", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3177723", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3177725", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Saturday, August 13, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3178034", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3178539", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3179574", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, October 11, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3179948", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, October 11, 2016 12:00:00 AM", "Description": "Update", "HotFixID": "KB3182203", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3184122", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3184943", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3185319", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Wednesday, September 14, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3185911", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" }, { "InstalledTime": "Tuesday, October 11, 2016 12:00:00 AM", "Description": "Security Update", "HotFixID": "KB3185331", - "InstalledBy": "NT AUTHORITY -SYSTEM" + "InstalledBy": "NT AUTHORITY\\SYSTEM" } ] }, @@ -1922,13 +1669,13 @@ SYSTEM" "tags": {}, "configurationItemVersion": "1.2", "configurationItemCaptureTime": "2016-10-26T19:11:44.151Z", - "configurationStateId": 1477509104151, - "awsAccountId": "123456789012", + "configurationStateId": "1477509104151", + "awsAccountId": "138920347130", "configurationItemStatus": "ResourceDiscovered", "resourceType": "AWS::SSM::ManagedInstanceInventory", "resourceId": "i-07f6b44c44bab9e8e", "resourceName": "", - "ARN": "arn:aws:ssm:us-east-1:123456789012:managed-instance-inventory/i-07f6b44c44bab9e8e", + "ARN": "arn:aws:ssm:us-east-1:138920347130:managed-instance-inventory/i-07f6b44c44bab9e8e", "awsRegion": "us-east-1", "availabilityZone": null, "configurationStateMd5Hash": "f5edb28b271ef50dddb2c5b08a535f14", diff --git a/rdk/template/runtime/dotnetcore1.0/CustomConfigHandler.cs b/rdk/template/runtime/dotnetcore1.0/CustomConfigHandler.cs deleted file mode 100644 index d5fefc36..00000000 --- a/rdk/template/runtime/dotnetcore1.0/CustomConfigHandler.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using System.Text; - -using System.Threading.Tasks; - -using Amazon.Lambda.Serialization.Json; -using Amazon.Lambda.Core; - -using Amazon.Lambda.ConfigEvents; -using Amazon.CloudWatchEvents; -using Amazon.ConfigService.Model; -using Amazon.ConfigService; -using Amazon.Runtime; -using Amazon.Lambda.Model; -using Newtonsoft.Json.Linq; - -// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] - -namespace Rdk -{ - public class CustomConfigHandler - { - public const String AWS_REGION_PROPERTY = "AWS_DEFAULT_REGION"; - public const String MESSAGE_TYPE_PROPERTY = "messageType"; - public const String HOST_ID = "hostId"; - public const String PLACEMENT = "placement"; - public const String CONFIGURATION = "configuration"; - public const String IMAGE_ID = "imageId"; - public const String STATUS_PATH = "configurationItemStatus"; - public const String TENANCY = "tenancy"; - public const String RESOURCE_DELETED = "ResourceDeleted"; - public const String RESOURCE_DELETED_NOT_RECORDED = "ResourceDeletedNotRecorded"; - public const String CAPTURE_TIME_PATH = "configurationItemCaptureTime"; - public const String CONFIGURATION_ITEM = "configurationItem"; - public const String RESOURCE_ID = "resourceId"; - public const String RESOURCE_NOT_RECORDED = "ResourceNotRecorded"; - public const String RESOURCE_TYPE = "resourceType"; - - - IAmazonConfigService ConfigService { get; set; } - - /// - /// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment - /// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the - /// region the Lambda function is executed in. - /// - public CustomConfigHandler() - { - Console.WriteLine("inside constructor..."); - } - - /// - /// Constructs an instance with a preconfigured S3 client. This can be used for testing the outside of the Lambda environment. - /// - /// - public CustomConfigHandler(IAmazonConfigService configService) - { - this.ConfigService = configService; - } - - /// - /// This method is called for every Lambda invocation. This method takes in an Config event object and can be used - /// to respond to Config notifications. - /// - /// - /// - /// Nothing - public async Task FunctionHandler(ConfigEvent evnt, ILambdaContext context) - { - Console.WriteLine("inside function handler..."); - Amazon.RegionEndpoint region = Amazon.RegionEndpoint.GetBySystemName(System.Environment.GetEnvironmentVariable(AWS_REGION_PROPERTY)); - AmazonConfigServiceClient configServiceClient = new AmazonConfigServiceClient(region); - await DoHandle(evnt, context, configServiceClient); - } - - private async Task DoHandle(ConfigEvent configEvent, ILambdaContext context, AmazonConfigServiceClient configServiceClient) - { - JObject ruleParamsObj; - JObject configItem; - - if (configEvent.RuleParameters != null){ - ruleParamsObj = JObject.Parse(configEvent.RuleParameters.ToString()); - } else { - ruleParamsObj = new JObject(); - } - - JObject invokingEventObj = JObject.Parse(configEvent.InvokingEvent.ToString()); - if(invokingEventObj["configurationItem"] != null){ - configItem = JObject.Parse(invokingEventObj[CONFIGURATION_ITEM].ToString()); - } else { - configItem = new JObject(); - } - - FailForIncompatibleEventTypes(invokingEventObj); - ComplianceType myCompliance = ComplianceType.NOT_APPLICABLE; - - if (!IsEventNotApplicable(configItem, configEvent.EventLeftScope)) - { - myCompliance = RuleCode.EvaluateCompliance(invokingEventObj, ruleParamsObj, context); - } - - // Associates the evaluation result with the AWS account published in the event. - Evaluation evaluation = new Evaluation { - ComplianceResourceId = GetResourceId(configItem), - ComplianceResourceType = GetResourceType(configItem), - OrderingTimestamp = GetCiCapturedTime(configItem), - ComplianceType = myCompliance - }; - - await DoPutEvaluations(configServiceClient, configEvent, evaluation); - } - - private String GetResourceType(JObject configItem) - { - return (String) configItem[RESOURCE_TYPE]; - } - - private void FailForIncompatibleEventTypes(JObject invokingEventObj) - { - String messageType = (String) invokingEventObj[MESSAGE_TYPE_PROPERTY]; - if (!IsCompatibleMessageType(messageType)) - { - throw new Exception(String.Format("Events with the message type '{0}' are not evaluated for this Config rule.", messageType)); - } - } - - private String GetResourceId(JObject configItem) - { - return (String) configItem[RESOURCE_ID]; - } - - private DateTime GetCiCapturedTime(JObject configItem) - { - return DateTime.Parse((String) configItem[CAPTURE_TIME_PATH]); - } - - private bool IsCompatibleMessageType(String messageType) - { - return String.Equals(MessageType.ConfigurationItemChangeNotification.ToString(), messageType); - } - - private bool IsEventNotApplicable(JObject configItem, bool eventLeftScope) - { - String status = configItem[STATUS_PATH].ToString(); - return (IsStatusNotApplicable(status) || eventLeftScope); - } - - private bool IsStatusNotApplicable(String status) - { - return String.Equals(RESOURCE_DELETED, status) - || String.Equals(RESOURCE_DELETED_NOT_RECORDED, status) - || String.Equals(RESOURCE_NOT_RECORDED, status); - } - - // Sends the evaluation results to AWS Config. - private async Task DoPutEvaluations(AmazonConfigServiceClient configClient, ConfigEvent configEvent, Evaluation evaluation) - { - Console.WriteLine("inside DoPutEvaluations..."); - PutEvaluationsRequest req = new PutEvaluationsRequest(); - req.Evaluations.Add(evaluation); - req.ResultToken = configEvent.ResultToken; - - - Task taskResp = configClient.PutEvaluationsAsync(req); - PutEvaluationsResponse response = await taskResp; - - // Ends the function execution if any evaluation results are not successfully reported. - if (response.FailedEvaluations.Count > 0) { - throw new Exception(String.Format( - "The following evaluations were not successfully reported to AWS Config: %s", - response.FailedEvaluations)); - } - } - - private DateTime GetDate(String dateString) - { - return DateTime.Parse(dateString, null, System.Globalization.DateTimeStyles.RoundtripKind); - } - - static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} diff --git a/rdk/template/runtime/dotnetcore1.0/RuleCode.cs b/rdk/template/runtime/dotnetcore1.0/RuleCode.cs deleted file mode 100755 index 4d376498..00000000 --- a/rdk/template/runtime/dotnetcore1.0/RuleCode.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Amazon.ConfigService.Model; -using Amazon.ConfigService; -using Amazon.Lambda.Core; -using Amazon.Lambda.Model; -using Amazon.Lambda.ConfigEvents; -using Newtonsoft.Json.Linq; - -namespace Rdk -{ - class RuleCode - { - public static ComplianceType EvaluateCompliance(JObject invokingEvent, JObject ruleParameters, ILambdaContext context) - { - context.Logger.LogLine("Beginning Custom Config Rule Evaluation"); - - /* - YOUR CODE GOES HERE! - */ - - return ComplianceType.NON_COMPLIANT; - } - } -} diff --git a/rdk/template/runtime/dotnetcore1.0/aws-lambda-tools-defaults.json b/rdk/template/runtime/dotnetcore1.0/aws-lambda-tools-defaults.json deleted file mode 100755 index 7cf6db07..00000000 --- a/rdk/template/runtime/dotnetcore1.0/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"default", - "region" : "us-west-2", - "configuration": "Release", - "framework": "netcoreapp1.0", - "function-runtime": "dotnetcore1.0", - "function-memory-size": 256, - "function-timeout": 30, - "function-handler": "csharp7.0::Rdk.CustomConfigHandler::FunctionHandler" -} diff --git a/rdk/template/runtime/dotnetcore1.0/csharp7.0.csproj b/rdk/template/runtime/dotnetcore1.0/csharp7.0.csproj deleted file mode 100644 index 08059c59..00000000 --- a/rdk/template/runtime/dotnetcore1.0/csharp7.0.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - netcoreapp1.0 - - - Exe - - - - - - - - - - - - - - - - - - - - - diff --git a/rdk/template/runtime/dotnetcore2.0/CustomConfigHandler.cs b/rdk/template/runtime/dotnetcore2.0/CustomConfigHandler.cs deleted file mode 100644 index d5fefc36..00000000 --- a/rdk/template/runtime/dotnetcore2.0/CustomConfigHandler.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using System.Text; - -using System.Threading.Tasks; - -using Amazon.Lambda.Serialization.Json; -using Amazon.Lambda.Core; - -using Amazon.Lambda.ConfigEvents; -using Amazon.CloudWatchEvents; -using Amazon.ConfigService.Model; -using Amazon.ConfigService; -using Amazon.Runtime; -using Amazon.Lambda.Model; -using Newtonsoft.Json.Linq; - -// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] - -namespace Rdk -{ - public class CustomConfigHandler - { - public const String AWS_REGION_PROPERTY = "AWS_DEFAULT_REGION"; - public const String MESSAGE_TYPE_PROPERTY = "messageType"; - public const String HOST_ID = "hostId"; - public const String PLACEMENT = "placement"; - public const String CONFIGURATION = "configuration"; - public const String IMAGE_ID = "imageId"; - public const String STATUS_PATH = "configurationItemStatus"; - public const String TENANCY = "tenancy"; - public const String RESOURCE_DELETED = "ResourceDeleted"; - public const String RESOURCE_DELETED_NOT_RECORDED = "ResourceDeletedNotRecorded"; - public const String CAPTURE_TIME_PATH = "configurationItemCaptureTime"; - public const String CONFIGURATION_ITEM = "configurationItem"; - public const String RESOURCE_ID = "resourceId"; - public const String RESOURCE_NOT_RECORDED = "ResourceNotRecorded"; - public const String RESOURCE_TYPE = "resourceType"; - - - IAmazonConfigService ConfigService { get; set; } - - /// - /// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment - /// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the - /// region the Lambda function is executed in. - /// - public CustomConfigHandler() - { - Console.WriteLine("inside constructor..."); - } - - /// - /// Constructs an instance with a preconfigured S3 client. This can be used for testing the outside of the Lambda environment. - /// - /// - public CustomConfigHandler(IAmazonConfigService configService) - { - this.ConfigService = configService; - } - - /// - /// This method is called for every Lambda invocation. This method takes in an Config event object and can be used - /// to respond to Config notifications. - /// - /// - /// - /// Nothing - public async Task FunctionHandler(ConfigEvent evnt, ILambdaContext context) - { - Console.WriteLine("inside function handler..."); - Amazon.RegionEndpoint region = Amazon.RegionEndpoint.GetBySystemName(System.Environment.GetEnvironmentVariable(AWS_REGION_PROPERTY)); - AmazonConfigServiceClient configServiceClient = new AmazonConfigServiceClient(region); - await DoHandle(evnt, context, configServiceClient); - } - - private async Task DoHandle(ConfigEvent configEvent, ILambdaContext context, AmazonConfigServiceClient configServiceClient) - { - JObject ruleParamsObj; - JObject configItem; - - if (configEvent.RuleParameters != null){ - ruleParamsObj = JObject.Parse(configEvent.RuleParameters.ToString()); - } else { - ruleParamsObj = new JObject(); - } - - JObject invokingEventObj = JObject.Parse(configEvent.InvokingEvent.ToString()); - if(invokingEventObj["configurationItem"] != null){ - configItem = JObject.Parse(invokingEventObj[CONFIGURATION_ITEM].ToString()); - } else { - configItem = new JObject(); - } - - FailForIncompatibleEventTypes(invokingEventObj); - ComplianceType myCompliance = ComplianceType.NOT_APPLICABLE; - - if (!IsEventNotApplicable(configItem, configEvent.EventLeftScope)) - { - myCompliance = RuleCode.EvaluateCompliance(invokingEventObj, ruleParamsObj, context); - } - - // Associates the evaluation result with the AWS account published in the event. - Evaluation evaluation = new Evaluation { - ComplianceResourceId = GetResourceId(configItem), - ComplianceResourceType = GetResourceType(configItem), - OrderingTimestamp = GetCiCapturedTime(configItem), - ComplianceType = myCompliance - }; - - await DoPutEvaluations(configServiceClient, configEvent, evaluation); - } - - private String GetResourceType(JObject configItem) - { - return (String) configItem[RESOURCE_TYPE]; - } - - private void FailForIncompatibleEventTypes(JObject invokingEventObj) - { - String messageType = (String) invokingEventObj[MESSAGE_TYPE_PROPERTY]; - if (!IsCompatibleMessageType(messageType)) - { - throw new Exception(String.Format("Events with the message type '{0}' are not evaluated for this Config rule.", messageType)); - } - } - - private String GetResourceId(JObject configItem) - { - return (String) configItem[RESOURCE_ID]; - } - - private DateTime GetCiCapturedTime(JObject configItem) - { - return DateTime.Parse((String) configItem[CAPTURE_TIME_PATH]); - } - - private bool IsCompatibleMessageType(String messageType) - { - return String.Equals(MessageType.ConfigurationItemChangeNotification.ToString(), messageType); - } - - private bool IsEventNotApplicable(JObject configItem, bool eventLeftScope) - { - String status = configItem[STATUS_PATH].ToString(); - return (IsStatusNotApplicable(status) || eventLeftScope); - } - - private bool IsStatusNotApplicable(String status) - { - return String.Equals(RESOURCE_DELETED, status) - || String.Equals(RESOURCE_DELETED_NOT_RECORDED, status) - || String.Equals(RESOURCE_NOT_RECORDED, status); - } - - // Sends the evaluation results to AWS Config. - private async Task DoPutEvaluations(AmazonConfigServiceClient configClient, ConfigEvent configEvent, Evaluation evaluation) - { - Console.WriteLine("inside DoPutEvaluations..."); - PutEvaluationsRequest req = new PutEvaluationsRequest(); - req.Evaluations.Add(evaluation); - req.ResultToken = configEvent.ResultToken; - - - Task taskResp = configClient.PutEvaluationsAsync(req); - PutEvaluationsResponse response = await taskResp; - - // Ends the function execution if any evaluation results are not successfully reported. - if (response.FailedEvaluations.Count > 0) { - throw new Exception(String.Format( - "The following evaluations were not successfully reported to AWS Config: %s", - response.FailedEvaluations)); - } - } - - private DateTime GetDate(String dateString) - { - return DateTime.Parse(dateString, null, System.Globalization.DateTimeStyles.RoundtripKind); - } - - static void Main(string[] args) - { - Console.WriteLine("Hello World!"); - } - } -} diff --git a/rdk/template/runtime/dotnetcore2.0/RuleCode.cs b/rdk/template/runtime/dotnetcore2.0/RuleCode.cs deleted file mode 100644 index 4d376498..00000000 --- a/rdk/template/runtime/dotnetcore2.0/RuleCode.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Amazon.ConfigService.Model; -using Amazon.ConfigService; -using Amazon.Lambda.Core; -using Amazon.Lambda.Model; -using Amazon.Lambda.ConfigEvents; -using Newtonsoft.Json.Linq; - -namespace Rdk -{ - class RuleCode - { - public static ComplianceType EvaluateCompliance(JObject invokingEvent, JObject ruleParameters, ILambdaContext context) - { - context.Logger.LogLine("Beginning Custom Config Rule Evaluation"); - - /* - YOUR CODE GOES HERE! - */ - - return ComplianceType.NON_COMPLIANT; - } - } -} diff --git a/rdk/template/runtime/dotnetcore2.0/aws-lambda-tools-defaults.json b/rdk/template/runtime/dotnetcore2.0/aws-lambda-tools-defaults.json deleted file mode 100644 index 7cf6db07..00000000 --- a/rdk/template/runtime/dotnetcore2.0/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"default", - "region" : "us-west-2", - "configuration": "Release", - "framework": "netcoreapp1.0", - "function-runtime": "dotnetcore1.0", - "function-memory-size": 256, - "function-timeout": 30, - "function-handler": "csharp7.0::Rdk.CustomConfigHandler::FunctionHandler" -} diff --git a/rdk/template/runtime/dotnetcore2.0/csharp7.0.csproj b/rdk/template/runtime/dotnetcore2.0/csharp7.0.csproj deleted file mode 100644 index dfbe6d79..00000000 --- a/rdk/template/runtime/dotnetcore2.0/csharp7.0.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - netcoreapp2.0 - - - Exe - - - - - - - - - - - - - - - - - - - - - diff --git a/rdk/template/runtime/nodejs4.3/rule_code.js b/rdk/template/runtime/nodejs4.3/rule_code.js deleted file mode 100644 index a8840f66..00000000 --- a/rdk/template/runtime/nodejs4.3/rule_code.js +++ /dev/null @@ -1,183 +0,0 @@ -'use strict'; - -const aws = require('aws-sdk'); - -const config = new aws.ConfigService(); - -function evaluateCompliance(configurationItem, ruleParameters, callback) { - - /* - ############################### - # Add your custom logic here. # - ############################### - */ - - callback('NOT_APPLICABLE'); -} - -//Boilerplate Code - You should not need to change anything below this comment. -function rule_handler(event, context, callback) { - //console.info(event); - const invokingEvent = JSON.parse(event.invokingEvent); - const configItem = invokingEvent.configurationItem; - const ruleParameters = JSON.parse(event.ruleParameters); - evaluateCompliance(configItem, ruleParameters, function(results){ - console.log(results); - callback(null, results); - }); -} - -// Helper function used to validate input -function checkDefined(reference, referenceName) { - if (!reference) { - throw new Error(`Error: ${referenceName} is not defined`); - } - return reference; -} - -// Check whether the message is OversizedConfigurationItemChangeNotification or not -function isOverSizedChangeNotification(messageType) { - checkDefined(messageType, 'messageType'); - return messageType === 'OversizedConfigurationItemChangeNotification'; -} - -// Check whether the message is a ScheduledNotification or not -function isScheduledNotification(messageType) { - checkDefined(messageType, 'messageType'); - return messageType === 'ScheduledNotification' -} - -// Get configurationItem using getResourceConfigHistory API. -function getConfiguration(resourceType, resourceId, configurationCaptureTime, callback) { - config.getResourceConfigHistory({ resourceType, resourceId, laterTime: new Date(configurationCaptureTime), limit: 1 }, (err, data) => { - if (err) { - callback(err, null); - } - const configurationItem = data.configurationItems[0]; - callback(null, configurationItem); - }); -} - -// Convert from the API model to the original invocation model -/*eslint no-param-reassign: ["error", { "props": false }]*/ -function convertApiConfiguration(apiConfiguration) { - apiConfiguration.awsAccountId = apiConfiguration.accountId; - apiConfiguration.ARN = apiConfiguration.arn; - apiConfiguration.configurationStateMd5Hash = apiConfiguration.configurationItemMD5Hash; - apiConfiguration.configurationItemVersion = apiConfiguration.version; - apiConfiguration.configuration = JSON.parse(apiConfiguration.configuration); - if ({}.hasOwnProperty.call(apiConfiguration, 'relationships')) { - for (let i = 0; i < apiConfiguration.relationships.length; i++) { - apiConfiguration.relationships[i].name = apiConfiguration.relationships[i].relationshipName; - } - } - return apiConfiguration; -} - -// Based on the type of message get the configuration item either from configurationItem in the invoking event or using the getResourceConfigHistory API in getConfiguration function. -function getConfigurationItem(invokingEvent, callback) { - checkDefined(invokingEvent, 'invokingEvent'); - if (isOverSizedChangeNotification(invokingEvent.messageType)) { - const configurationItemSummary = checkDefined(invokingEvent.configurationItemSummary, 'configurationItemSummary'); - getConfiguration(configurationItemSummary.resourceType, configurationItemSummary.resourceId, configurationItemSummary.configurationItemCaptureTime, (err, apiConfigurationItem) => { - if (err) { - callback(err); - } - const configurationItem = convertApiConfiguration(apiConfigurationItem); - callback(null, configurationItem); - }); - } else if (isScheduledNotification(invokingEvent.messageType)) { - callback(null, null) - } else { - checkDefined(invokingEvent.configurationItem, 'configurationItem'); - callback(null, invokingEvent.configurationItem); - } -} - -// Check whether the resource has been deleted. If it has, then the evaluation is unnecessary. -function isApplicable(configurationItem, event) { - //checkDefined(configurationItem, 'configurationItem'); - checkDefined(event, 'event'); - //const status = configurationItem.configurationItemStatus; - const eventLeftScope = event.eventLeftScope; - //return (status === 'OK' || status === 'ResourceDiscovered') && eventLeftScope === false; - return (eventLeftScope === false); -} - -// This is the handler that's invoked by Lambda -// Most of this code is boilerplate; use as is -exports.lambda_handler = function(event, context, callback) { - checkDefined(event, 'event'); - const invokingEvent = JSON.parse(event.invokingEvent); - const ruleParameters = JSON.parse(event.ruleParameters); - getConfigurationItem(invokingEvent, (err, configurationItem) => { - if (err) { - callback(err); - } - //let compliance = 'NOT_APPLICABLE'; - if (isApplicable(configurationItem, event)) { - invokingEvent.configurationItem = configurationItem; - event.invokingEvent = JSON.stringify(invokingEvent); - rule_handler(event, context, (err, compliance_results) => { - if (err) { - callback(err); - } - //compliance = computedCompliance; - var putEvaluationsRequest = {}; - - // Put together the request that reports the evaluation status - if (typeof compliance_results === 'string' || compliance_results instanceof String){ - putEvaluationsRequest.Evaluations = [ - { - ComplianceResourceType: configurationItem.resourceType, - ComplianceResourceId: configurationItem.resourceId, - ComplianceType: compliance_results, - OrderingTimestamp: configurationItem.configurationItemCaptureTime - } - ]; - } else if (compliance_results instanceof Array) { - putEvaluationsRequest.Evaluations = []; - - var fields = ['ComplianceResourceType', 'ComplianceResourceId', 'ComplianceType', 'OrderingTimestamp']; - - for (var i = 0; i < compliance_results.length; i++) { - var missing_fields = false; - for (var j = 0; j < fields.length; j++) { - if (!compliance_results[i].hasOwnProperty(fields[j])) { - console.info("Missing " + fields[j] + " from custom evaluation."); - missing_fields = true; - } - } - - if (!missing_fields){ - putEvaluationsRequest.Evaluations.push(compliance_results[i]); - } - } - } else { - putEvaluationsRequest.Evaluations = [ - { - ComplianceResourceType: configurationItem.resourceType, - ComplianceResourceId: configurationItem.resourceId, - ComplianceType: 'INSUFFICIENT_DATA', - OrderingTimestamp: configurationItem.configurationItemCaptureTime - } - ]; - } - - putEvaluationsRequest.ResultToken = event.resultToken; - - // Invoke the Config API to report the result of the evaluation - config.putEvaluations(putEvaluationsRequest, (error, data) => { - if (error) { - callback(error, null); - } else if (data.FailedEvaluations.length > 0) { - // Ends the function execution if any evaluation results are not successfully reported. - callback(JSON.stringify(data), null); - } else { - callback(null, data); - } - }); - }); - } - }); -}; diff --git a/rdk/template/runtime/nodejs6.10/rule_code.js b/rdk/template/runtime/nodejs6.10/rule_code.js deleted file mode 100644 index bb90065a..00000000 --- a/rdk/template/runtime/nodejs6.10/rule_code.js +++ /dev/null @@ -1,215 +0,0 @@ -"use strict"; - -const aws = require("aws-sdk"); - -const config = new aws.ConfigService(); - -function evaluateCompliance(configurationItem, ruleParameters, callback) { - /* - ############################### - # Add your custom logic here. # - ############################### - */ - - callback("NOT_APPLICABLE"); -} - -//Boilerplate Code - You should not need to change anything below this comment. -function rule_handler(event, context, callback) { - //console.info(event); - const invokingEvent = JSON.parse(event.invokingEvent); - const configItem = invokingEvent.configurationItem; - const ruleParameters = JSON.parse(event.ruleParameters); - evaluateCompliance(configItem, ruleParameters, function (results) { - console.log(results); - callback(null, results); - }); -} - -// Helper function used to validate input -function checkDefined(reference, referenceName) { - if (!reference) { - throw new Error(`Error: ${referenceName} is not defined`); - } - return reference; -} - -// Check whether the message is OversizedConfigurationItemChangeNotification or not -function isOverSizedChangeNotification(messageType) { - checkDefined(messageType, "messageType"); - return messageType === "OversizedConfigurationItemChangeNotification"; -} - -// Check whether the message is a ScheduledNotification or not -function isScheduledNotification(messageType) { - checkDefined(messageType, "messageType"); - return messageType === "ScheduledNotification"; -} - -// Get configurationItem using getResourceConfigHistory API. -function getConfiguration( - resourceType, - resourceId, - configurationCaptureTime, - callback -) { - config.getResourceConfigHistory( - { - resourceType, - resourceId, - laterTime: new Date(configurationCaptureTime), - limit: 1, - }, - (err, data) => { - if (err) { - callback(err, null); - } - const configurationItem = data.configurationItems[0]; - callback(null, configurationItem); - } - ); -} - -// Convert from the API model to the original invocation model -/*eslint no-param-reassign: ["error", { "props": false }]*/ -function convertApiConfiguration(apiConfiguration) { - apiConfiguration.awsAccountId = apiConfiguration.accountId; - apiConfiguration.ARN = apiConfiguration.arn; - apiConfiguration.configurationStateMd5Hash = - apiConfiguration.configurationItemMD5Hash; - apiConfiguration.configurationItemVersion = apiConfiguration.version; - apiConfiguration.configuration = JSON.parse(apiConfiguration.configuration); - if ({}.hasOwnProperty.call(apiConfiguration, "relationships")) { - for (let i = 0; i < apiConfiguration.relationships.length; i++) { - apiConfiguration.relationships[i].name = - apiConfiguration.relationships[i].relationshipName; - } - } - return apiConfiguration; -} - -// Based on the type of message get the configuration item either from configurationItem in the invoking event or using the getResourceConfigHistory API in getConfiguration function. -function getConfigurationItem(invokingEvent, callback) { - checkDefined(invokingEvent, "invokingEvent"); - if (isOverSizedChangeNotification(invokingEvent.messageType)) { - const configurationItemSummary = checkDefined( - invokingEvent.configurationItemSummary, - "configurationItemSummary" - ); - getConfiguration( - configurationItemSummary.resourceType, - configurationItemSummary.resourceId, - configurationItemSummary.configurationItemCaptureTime, - (err, apiConfigurationItem) => { - if (err) { - callback(err); - } - const configurationItem = convertApiConfiguration(apiConfigurationItem); - callback(null, configurationItem); - } - ); - } else if (isScheduledNotification(invokingEvent.messageType)) { - callback(null, null); - } else { - checkDefined(invokingEvent.configurationItem, "configurationItem"); - callback(null, invokingEvent.configurationItem); - } -} - -// Check whether the resource has been deleted. If it has, then the evaluation is unnecessary. -function isApplicable(configurationItem, event) { - //checkDefined(configurationItem, 'configurationItem'); - checkDefined(event, "event"); - //const status = configurationItem.configurationItemStatus; - const eventLeftScope = event.eventLeftScope; - //return (status === 'OK' || status === 'ResourceDiscovered') && eventLeftScope === false; - return eventLeftScope === false; -} - -// This is the handler that's invoked by Lambda -// Most of this code is boilerplate; use as is -exports.lambda_handler = function (event, context, callback) { - checkDefined(event, "event"); - const invokingEvent = JSON.parse(event.invokingEvent); - const ruleParameters = JSON.parse(event.ruleParameters); - getConfigurationItem(invokingEvent, (err, configurationItem) => { - if (err) { - callback(err); - } - //let compliance = 'NOT_APPLICABLE'; - if (isApplicable(configurationItem, event)) { - invokingEvent.configurationItem = configurationItem; - event.invokingEvent = JSON.stringify(invokingEvent); - rule_handler(event, context, (err, compliance_results) => { - if (err) { - callback(err); - } - //compliance = computedCompliance; - var putEvaluationsRequest = {}; - - // Put together the request that reports the evaluation status - if ( - typeof compliance_results === "string" || - compliance_results instanceof String - ) { - putEvaluationsRequest.Evaluations = [ - { - ComplianceResourceType: configurationItem.resourceType, - ComplianceResourceId: configurationItem.resourceId, - ComplianceType: compliance_results, - OrderingTimestamp: configurationItem.configurationItemCaptureTime, - }, - ]; - } else if (compliance_results instanceof Array) { - putEvaluationsRequest.Evaluations = []; - - var fields = [ - "ComplianceResourceType", - "ComplianceResourceId", - "ComplianceType", - "OrderingTimestamp", - ]; - - for (var i = 0; i < compliance_results.length; i++) { - var missing_fields = false; - for (var j = 0; j < fields.length; j++) { - if (!compliance_results[i].hasOwnProperty(fields[j])) { - console.info( - "Missing " + fields[j] + " from custom evaluation." - ); - missing_fields = true; - } - } - - if (!missing_fields) { - putEvaluationsRequest.Evaluations.push(compliance_results[i]); - } - } - } else { - putEvaluationsRequest.Evaluations = [ - { - ComplianceResourceType: configurationItem.resourceType, - ComplianceResourceId: configurationItem.resourceId, - ComplianceType: "INSUFFICIENT_DATA", - OrderingTimestamp: configurationItem.configurationItemCaptureTime, - }, - ]; - } - - putEvaluationsRequest.ResultToken = event.resultToken; - - // Invoke the Config API to report the result of the evaluation - config.putEvaluations(putEvaluationsRequest, (error, data) => { - if (error) { - callback(error, null); - } else if (data.FailedEvaluations.length > 0) { - // Ends the function execution if any evaluation results are not successfully reported. - callback(JSON.stringify(data), null); - } else { - callback(null, data); - } - }); - }); - } - }); -}; diff --git a/rdk/template/runtime/python3.10-lib/rule_code.py b/rdk/template/runtime/python3.10-lib/rule_code.py new file mode 100644 index 00000000..90fdebe2 --- /dev/null +++ b/rdk/template/runtime/python3.10-lib/rule_code.py @@ -0,0 +1,25 @@ +from rdklib import Evaluator, Evaluation, ConfigRule, ComplianceType +<%ApplicableResources1%> +class <%RuleName%>(ConfigRule): + def evaluate_change(self, event, client_factory, configuration_item, valid_rule_parameters): + ############################### + # Add your custom logic here. # + ############################### + + return [Evaluation(ComplianceType.NOT_APPLICABLE)] + + #def evaluate_periodic(self, event, client_factory, valid_rule_parameters): + # pass + + def evaluate_parameters(self, rule_parameters): + valid_rule_parameters = rule_parameters + return valid_rule_parameters + + +################################ +# DO NOT MODIFY ANYTHING BELOW # +################################ +def lambda_handler(event, context): + my_rule = <%RuleName%>() + evaluator = Evaluator(my_rule<%ApplicableResources2%>) + return evaluator.handle(event, context) diff --git a/rdk/template/runtime/python3.10-lib/rule_test.py b/rdk/template/runtime/python3.10-lib/rule_test.py new file mode 100644 index 00000000..db0cf30c --- /dev/null +++ b/rdk/template/runtime/python3.10-lib/rule_test.py @@ -0,0 +1,157 @@ +import datetime +import json +import logging +import unittest +from unittest.mock import patch, MagicMock +from botocore.exceptions import ClientError +from rdklib import Evaluation, ComplianceType +import rdklibtest + +############## +# Parameters # +############## + +# Define the default resource to report to Config Rules +# TODO - Replace with your resource type +RESOURCE_TYPE = "AWS::IAM::Role" + +############# +# Main Code # +############# + +MODULE = __import__("check_security_hub_aggregator") +RULE = MODULE.check_security_hub_aggregator() + +CLIENT_FACTORY = MagicMock() + +# example for mocking IAM API calls +IAM_CLIENT_MOCK = MagicMock() +# STS client for getting account ID +STS_CLIENT_MOCK = MagicMock() + + +def mock_get_client(client_name, *args, **kwargs): + if client_name == "iam": + return IAM_CLIENT_MOCK + if client_name == "sts": + return STS_CLIENT_MOCK + raise Exception("Attempting to create an unknown client") + + +@patch.object(CLIENT_FACTORY, "build_client", MagicMock(side_effect=mock_get_client)) +class ComplianceTest(unittest.TestCase): + rule_parameters = { + "SomeParameterKey": "SomeParameterValue", + "SomeParameterKey2": "SomeParameterValue2", + } + + role_sample_configuration_abridged = {"arn": "some-arn", "roleName": "testrole"} + + invoking_event_iam_role_sample = { + "configurationItem": { + "relatedEvents": [], + "relationships": [], + "configuration": role_sample_configuration_abridged, + "tags": {}, + "configurationItemCaptureTime": "2018-07-02T03:37:52.418Z", + "awsAccountId": "123456789012", + "configurationItemStatus": "ResourceDiscovered", + "resourceType": "AWS::IAM::Role", + "resourceId": "some-resource-id", + "resourceName": "some-resource-name", + "ARN": "some-arn", + }, + "notificationCreationTime": "2018-07-02T23:05:34.445Z", + "messageType": "ConfigurationItemChangeNotification", + "executionRoleArn": "arn:aws:dummy", + } + + list_roles_response = { + "Roles": [ + { + "Path": "/", + "RoleName": "testrole", + "RoleId": "some-role-id", + "Arn": "arn:aws:iam::111111111111:role/testrole", + "CreateDate": datetime.datetime(2015, 1, 1), + "Description": "this is a test role", + "MaxSessionDuration": 123, + "Tags": [ + {"Key": "one_tag", "Value": "its_value"}, + ], + "RoleLastUsed": { + "LastUsedDate": datetime.datetime(2015, 1, 1), + "Region": "us-east-1", + }, + }, + ] + } + test_account_id = "111111111111" + get_caller_identity_response = {"Account": test_account_id} + + def setUp(self): + STS_CLIENT_MOCK.reset_mock() + + def test_sample(self): + self.assertTrue(True) + + # Example of how to evaluate a configuration change rule + def test_configurationchange_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + response = RULE.evaluate_change( + event=json.dumps(self.invoking_event_iam_role_sample), + client_factory=CLIENT_FACTORY, + configuration_item=self.role_sample_configuration_abridged, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + annotation="This is a configuration change rule's annotation.", + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("resourceId", None), + resourceType=RESOURCE_TYPE, + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) + + # Example of how to mock the client response for a list_roles API call + def test_periodic_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + IAM_CLIENT_MOCK.list_roles = MagicMock(return_value=self.list_roles_response) + # Example of how to evaluate a periodic rule + response = RULE.evaluate_periodic( + event=rdklibtest.create_test_scheduled_event(self.rule_parameters), + client_factory=CLIENT_FACTORY, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("awsAccountId", None), + resourceType="AWS::::Account", + annotation="This is a periodic rule's annotation.", + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/rdk/template/runtime/python3.10/rule_code.py b/rdk/template/runtime/python3.10/rule_code.py new file mode 100644 index 00000000..682297b0 --- /dev/null +++ b/rdk/template/runtime/python3.10/rule_code.py @@ -0,0 +1,437 @@ +import json +import sys +import datetime +import boto3 +import botocore + +try: + import liblogging +except ImportError: + pass + +############## +# Parameters # +############## + +# Define the default resource to report to Config Rules +DEFAULT_RESOURCE_TYPE = "AWS::::Account" + +# Set to True to get the lambda to assume the Role attached on the Config Service (useful for cross-account). +ASSUME_ROLE_MODE = False + +# Other parameters (no change needed) +CONFIG_ROLE_TIMEOUT_SECONDS = 900 + +############# +# Main Code # +############# + + +def evaluate_compliance(event, configuration_item, valid_rule_parameters): + """Form the evaluation(s) to be return to Config Rules + + Return either: + None -- when no result needs to be displayed + a string -- either COMPLIANT, NON_COMPLIANT or NOT_APPLICABLE + a dictionary -- the evaluation dictionary, usually built by build_evaluation_from_config_item() + a list of dictionary -- a list of evaluation dictionary , usually built by build_evaluation() + + Keyword arguments: + event -- the event variable given in the lambda handler + configuration_item -- the configurationItem dictionary in the invokingEvent + valid_rule_parameters -- the output of the evaluate_parameters() representing validated parameters of the Config Rule + + Advanced Notes: + 1 -- if a resource is deleted and generate a configuration change with ResourceDeleted status, the Boilerplate code will put a NOT_APPLICABLE on this resource automatically. + 2 -- if a None or a list of dictionary is returned, the old evaluation(s) which are not returned in the new evaluation list are returned as NOT_APPLICABLE by the Boilerplate code + 3 -- if None or an empty string, list or dict is returned, the Boilerplate code will put a "shadow" evaluation to feedback that the evaluation took place properly + """ + + ############################### + # Add your custom logic here. # + ############################### + + return "NOT_APPLICABLE" + + +def evaluate_parameters(rule_parameters): + """Evaluate the rule parameters dictionary validity. Raise a ValueError for invalid parameters. + + Return: + anything suitable for the evaluate_compliance() + + Keyword arguments: + rule_parameters -- the Key/Value dictionary of the Config Rules parameters + """ + valid_rule_parameters = rule_parameters + return valid_rule_parameters + + +#################### +# Helper Functions # +#################### + +# Build an error to be displayed in the logs when the parameter is invalid. +def build_parameters_value_error_response(ex): + """Return an error dictionary when the evaluate_parameters() raises a ValueError. + + Keyword arguments: + ex -- Exception text + """ + return build_error_response( + internal_error_message="Parameter value is invalid", + internal_error_details="An ValueError was raised during the validation of the Parameter value", + customer_error_code="InvalidParameterValueException", + customer_error_message=str(ex), + ) + + +# This gets the client after assuming the Config service role +# either in the same AWS account or cross-account. +def get_client(service, event, region=None): + """Return the service boto client. It should be used instead of directly calling the client. + + Keyword arguments: + service -- the service name used for calling the boto.client() + event -- the event variable given in the lambda handler + region -- the region where the client is called (default: None) + """ + if not ASSUME_ROLE_MODE: + return boto3.client(service, region) + credentials = get_assume_role_credentials(get_execution_role_arn(event), region) + return boto3.client( + service, + aws_access_key_id=credentials["AccessKeyId"], + aws_secret_access_key=credentials["SecretAccessKey"], + aws_session_token=credentials["SessionToken"], + region_name=region, + ) + + +# This generates an evaluation for config +def build_evaluation(resource_id, compliance_type, event, resource_type=DEFAULT_RESOURCE_TYPE, annotation=None): + """Form an evaluation as a dictionary. Usually suited to report on scheduled rules. + + Keyword arguments: + resource_id -- the unique id of the resource to report + compliance_type -- either COMPLIANT, NON_COMPLIANT or NOT_APPLICABLE + event -- the event variable given in the lambda handler + resource_type -- the CloudFormation resource type (or AWS::::Account) to report on the rule (default DEFAULT_RESOURCE_TYPE) + annotation -- an annotation to be added to the evaluation (default None). It will be truncated to 255 if longer. + """ + eval_cc = {} + if annotation: + eval_cc["Annotation"] = build_annotation(annotation) + eval_cc["ComplianceResourceType"] = resource_type + eval_cc["ComplianceResourceId"] = resource_id + eval_cc["ComplianceType"] = compliance_type + eval_cc["OrderingTimestamp"] = str(json.loads(event["invokingEvent"])["notificationCreationTime"]) + return eval_cc + + +def build_evaluation_from_config_item(configuration_item, compliance_type, annotation=None): + """Form an evaluation as a dictionary. Usually suited to report on configuration change rules. + + Keyword arguments: + configuration_item -- the configurationItem dictionary in the invokingEvent + compliance_type -- either COMPLIANT, NON_COMPLIANT or NOT_APPLICABLE + annotation -- an annotation to be added to the evaluation (default None). It will be truncated to 255 if longer. + """ + eval_ci = {} + if annotation: + eval_ci["Annotation"] = build_annotation(annotation) + eval_ci["ComplianceResourceType"] = configuration_item["resourceType"] + eval_ci["ComplianceResourceId"] = configuration_item["resourceId"] + eval_ci["ComplianceType"] = compliance_type + eval_ci["OrderingTimestamp"] = configuration_item["configurationItemCaptureTime"] + return eval_ci + + +#################### +# Boilerplate Code # +#################### + +# Get execution role for Lambda function +def get_execution_role_arn(event): + role_arn = None + if "ruleParameters" in event: + rule_params = json.loads(event["ruleParameters"]) + role_name = rule_params.get("ExecutionRoleName") + if role_name: + execution_role_prefix = event["executionRoleArn"].split("/")[0] + role_arn = "{}/{}".format(execution_role_prefix, role_name) + + if not role_arn: + role_arn = event["executionRoleArn"] + + return role_arn + + +# Build annotation within Service constraints +def build_annotation(annotation_string): + if len(annotation_string) > 256: + return annotation_string[:244] + " [truncated]" + return annotation_string + + +# Helper function used to validate input +def check_defined(reference, reference_name): + if not reference: + raise Exception("Error: ", reference_name, "is not defined") + return reference + + +# Check whether the message is OversizedConfigurationItemChangeNotification or not +def is_oversized_changed_notification(message_type): + check_defined(message_type, "messageType") + return message_type == "OversizedConfigurationItemChangeNotification" + + +# Check whether the message is a ScheduledNotification or not. +def is_scheduled_notification(message_type): + check_defined(message_type, "messageType") + return message_type == "ScheduledNotification" + + +# Get configurationItem using getResourceConfigHistory API +# in case of OversizedConfigurationItemChangeNotification +def get_configuration(resource_type, resource_id, configuration_capture_time): + result = AWS_CONFIG_CLIENT.get_resource_config_history( + resourceType=resource_type, resourceId=resource_id, laterTime=configuration_capture_time, limit=1 + ) + configuration_item = result["configurationItems"][0] + return convert_api_configuration(configuration_item) + + +# Convert from the API model to the original invocation model +def convert_api_configuration(configuration_item): + for k, v in configuration_item.items(): + if isinstance(v, datetime.datetime): + configuration_item[k] = str(v) + configuration_item["awsAccountId"] = configuration_item["accountId"] + configuration_item["ARN"] = configuration_item["arn"] + configuration_item["configurationStateMd5Hash"] = configuration_item["configurationItemMD5Hash"] + configuration_item["configurationItemVersion"] = configuration_item["version"] + configuration_item["configuration"] = json.loads(configuration_item["configuration"]) + if "relationships" in configuration_item: + for i in range(len(configuration_item["relationships"])): + configuration_item["relationships"][i]["name"] = configuration_item["relationships"][i]["relationshipName"] + return configuration_item + + +# Based on the type of message get the configuration item +# either from configurationItem in the invoking event +# or using the getResourceConfigHistory API in getConfiguration function. +def get_configuration_item(invoking_event): + check_defined(invoking_event, "invokingEvent") + if is_oversized_changed_notification(invoking_event["messageType"]): + configuration_item_summary = check_defined( + invoking_event["configurationItemSummary"], "configurationItemSummary" + ) + return get_configuration( + configuration_item_summary["resourceType"], + configuration_item_summary["resourceId"], + configuration_item_summary["configurationItemCaptureTime"], + ) + if is_scheduled_notification(invoking_event["messageType"]): + return None + return check_defined(invoking_event["configurationItem"], "configurationItem") + + +# Check whether the resource has been deleted. If it has, then the evaluation is unnecessary. +def is_applicable(configuration_item, event): + try: + check_defined(configuration_item, "configurationItem") + check_defined(event, "event") + except: + return True + status = configuration_item["configurationItemStatus"] + event_left_scope = event["eventLeftScope"] + if status == "ResourceDeleted": + print("Resource Deleted, setting Compliance Status to NOT_APPLICABLE.") + + return status in ("OK", "ResourceDiscovered") and not event_left_scope + + +def get_assume_role_credentials(role_arn, region=None): + sts_client = boto3.client("sts", region) + try: + assume_role_response = sts_client.assume_role( + RoleArn=role_arn, RoleSessionName="configLambdaExecution", DurationSeconds=CONFIG_ROLE_TIMEOUT_SECONDS + ) + if "liblogging" in sys.modules: + liblogging.logSession(role_arn, assume_role_response) + return assume_role_response["Credentials"] + except botocore.exceptions.ClientError as ex: + # Scrub error message for any internal account info leaks + print(str(ex)) + if "AccessDenied" in ex.response["Error"]["Code"]: + ex.response["Error"]["Message"] = "AWS Config does not have permission to assume the IAM role." + else: + ex.response["Error"]["Message"] = "InternalError" + ex.response["Error"]["Code"] = "InternalError" + raise ex + + +# This removes older evaluation (usually useful for periodic rule not reporting on AWS::::Account). +def clean_up_old_evaluations(latest_evaluations, event): + + cleaned_evaluations = [] + + old_eval = AWS_CONFIG_CLIENT.get_compliance_details_by_config_rule( + ConfigRuleName=event["configRuleName"], ComplianceTypes=["COMPLIANT", "NON_COMPLIANT"], Limit=100 + ) + + old_eval_list = [] + + while True: + for old_result in old_eval["EvaluationResults"]: + old_eval_list.append(old_result) + if "NextToken" in old_eval: + next_token = old_eval["NextToken"] + old_eval = AWS_CONFIG_CLIENT.get_compliance_details_by_config_rule( + ConfigRuleName=event["configRuleName"], + ComplianceTypes=["COMPLIANT", "NON_COMPLIANT"], + Limit=100, + NextToken=next_token, + ) + else: + break + + for old_eval in old_eval_list: + old_resource_id = old_eval["EvaluationResultIdentifier"]["EvaluationResultQualifier"]["ResourceId"] + newer_founded = False + for latest_eval in latest_evaluations: + if old_resource_id == latest_eval["ComplianceResourceId"]: + newer_founded = True + if not newer_founded: + cleaned_evaluations.append(build_evaluation(old_resource_id, "NOT_APPLICABLE", event)) + + return cleaned_evaluations + latest_evaluations + + +def lambda_handler(event, context): + if "liblogging" in sys.modules: + liblogging.logEvent(event) + + global AWS_CONFIG_CLIENT + + # print(event) + check_defined(event, "event") + invoking_event = json.loads(event["invokingEvent"]) + rule_parameters = {} + if "ruleParameters" in event: + rule_parameters = json.loads(event["ruleParameters"]) + + try: + valid_rule_parameters = evaluate_parameters(rule_parameters) + except ValueError as ex: + return build_parameters_value_error_response(ex) + + try: + AWS_CONFIG_CLIENT = get_client("config", event) + if invoking_event["messageType"] in [ + "ConfigurationItemChangeNotification", + "ScheduledNotification", + "OversizedConfigurationItemChangeNotification", + ]: + configuration_item = get_configuration_item(invoking_event) + if is_applicable(configuration_item, event): + compliance_result = evaluate_compliance(event, configuration_item, valid_rule_parameters) + else: + compliance_result = "NOT_APPLICABLE" + else: + return build_internal_error_response("Unexpected message type", str(invoking_event)) + except botocore.exceptions.ClientError as ex: + if is_internal_error(ex): + return build_internal_error_response("Unexpected error while completing API request", str(ex)) + return build_error_response( + "Customer error while making API request", + str(ex), + ex.response["Error"]["Code"], + ex.response["Error"]["Message"], + ) + except ValueError as ex: + return build_internal_error_response(str(ex), str(ex)) + + evaluations = [] + latest_evaluations = [] + + if not compliance_result: + latest_evaluations.append( + build_evaluation(event["accountId"], "NOT_APPLICABLE", event, resource_type="AWS::::Account") + ) + evaluations = clean_up_old_evaluations(latest_evaluations, event) + elif isinstance(compliance_result, str): + if configuration_item: + evaluations.append(build_evaluation_from_config_item(configuration_item, compliance_result)) + else: + evaluations.append( + build_evaluation(event["accountId"], compliance_result, event, resource_type=DEFAULT_RESOURCE_TYPE) + ) + elif isinstance(compliance_result, list): + for evaluation in compliance_result: + missing_fields = False + for field in ("ComplianceResourceType", "ComplianceResourceId", "ComplianceType", "OrderingTimestamp"): + if field not in evaluation: + print("Missing " + field + " from custom evaluation.") + missing_fields = True + + if not missing_fields: + latest_evaluations.append(evaluation) + evaluations = clean_up_old_evaluations(latest_evaluations, event) + elif isinstance(compliance_result, dict): + missing_fields = False + for field in ("ComplianceResourceType", "ComplianceResourceId", "ComplianceType", "OrderingTimestamp"): + if field not in compliance_result: + print("Missing " + field + " from custom evaluation.") + missing_fields = True + if not missing_fields: + evaluations.append(compliance_result) + else: + evaluations.append(build_evaluation_from_config_item(configuration_item, "NOT_APPLICABLE")) + + # Put together the request that reports the evaluation status + result_token = event["resultToken"] + test_mode = False + if result_token == "TESTMODE": + # Used solely for RDK test to skip actual put_evaluation API call + test_mode = True + + # Invoke the Config API to report the result of the evaluation + evaluation_copy = [] + evaluation_copy = evaluations[:] + while evaluation_copy: + AWS_CONFIG_CLIENT.put_evaluations( + Evaluations=evaluation_copy[:100], ResultToken=result_token, TestMode=test_mode + ) + del evaluation_copy[:100] + + # Used solely for RDK test to be able to test Lambda function + return evaluations + + +def is_internal_error(exception): + return ( + (not isinstance(exception, botocore.exceptions.ClientError)) + or exception.response["Error"]["Code"].startswith("5") + or "InternalError" in exception.response["Error"]["Code"] + or "ServiceError" in exception.response["Error"]["Code"] + ) + + +def build_internal_error_response(internal_error_message, internal_error_details=None): + return build_error_response(internal_error_message, internal_error_details, "InternalError", "InternalError") + + +def build_error_response( + internal_error_message, internal_error_details=None, customer_error_code=None, customer_error_message=None +): + error_response = { + "internalErrorMessage": internal_error_message, + "internalErrorDetails": internal_error_details, + "customerErrorMessage": customer_error_message, + "customerErrorCode": customer_error_code, + } + print(error_response) + return error_response diff --git a/rdk/template/runtime/python3.10/rule_test.py b/rdk/template/runtime/python3.10/rule_test.py new file mode 100644 index 00000000..e0f8c974 --- /dev/null +++ b/rdk/template/runtime/python3.10/rule_test.py @@ -0,0 +1,177 @@ +import sys +import unittest +from unittest.mock import MagicMock +import botocore + +############## +# Parameters # +############## + +# Define the default resource to report to Config Rules +DEFAULT_RESOURCE_TYPE = "AWS::::Account" + +############# +# Main Code # +############# + +CONFIG_CLIENT_MOCK = MagicMock() +STS_CLIENT_MOCK = MagicMock() + + +class Boto3Mock: + @staticmethod + def client(client_name, *args, **kwargs): + if client_name == "config": + return CONFIG_CLIENT_MOCK + if client_name == "sts": + return STS_CLIENT_MOCK + raise Exception("Attempting to create an unknown client") + + +sys.modules["boto3"] = Boto3Mock() + +RULE = __import__("<%RuleName%>") + + +class ComplianceTest(unittest.TestCase): + + rule_parameters = '{"SomeParameterKey":"SomeParameterValue","SomeParameterKey2":"SomeParameterValue2"}' + + invoking_event_iam_role_sample = '{"configurationItem":{"relatedEvents":[],"relationships":[],"configuration":{},"tags":{},"configurationItemCaptureTime":"2018-07-02T03:37:52.418Z","awsAccountId":"123456789012","configurationItemStatus":"ResourceDiscovered","resourceType":"AWS::IAM::Role","resourceId":"some-resource-id","resourceName":"some-resource-name","ARN":"some-arn"},"notificationCreationTime":"2018-07-02T23:05:34.445Z","messageType":"ConfigurationItemChangeNotification"}' + + def setUp(self): + pass + + def test_sample(self): + self.assertTrue(True) + + # def test_sample_2(self): + # RULE.ASSUME_ROLE_MODE = False + # response = RULE.lambda_handler(build_lambda_configurationchange_event(self.invoking_event_iam_role_sample, self.rule_parameters), {}) + # resp_expected = [] + # resp_expected.append(build_expected_response('NOT_APPLICABLE', 'some-resource-id', 'AWS::IAM::Role')) + # assert_successful_evaluation(self, response, resp_expected) + + +#################### +# Helper Functions # +#################### + + +def build_lambda_configurationchange_event(invoking_event, rule_parameters=None): + event_to_return = { + "configRuleName": "myrule", + "executionRoleArn": "roleArn", + "eventLeftScope": False, + "invokingEvent": invoking_event, + "accountId": "123456789012", + "configRuleArn": "arn:aws:config:us-east-1:123456789012:config-rule/config-rule-8fngan", + "resultToken": "token", + } + if rule_parameters: + event_to_return["ruleParameters"] = rule_parameters + return event_to_return + + +def build_lambda_scheduled_event(rule_parameters=None): + invoking_event = '{"messageType":"ScheduledNotification","notificationCreationTime":"2017-12-23T22:11:18.158Z"}' + event_to_return = { + "configRuleName": "myrule", + "executionRoleArn": "roleArn", + "eventLeftScope": False, + "invokingEvent": invoking_event, + "accountId": "123456789012", + "configRuleArn": "arn:aws:config:us-east-1:123456789012:config-rule/config-rule-8fngan", + "resultToken": "token", + } + if rule_parameters: + event_to_return["ruleParameters"] = rule_parameters + return event_to_return + + +def build_expected_response( + compliance_type, compliance_resource_id, compliance_resource_type=DEFAULT_RESOURCE_TYPE, annotation=None +): + if not annotation: + return { + "ComplianceType": compliance_type, + "ComplianceResourceId": compliance_resource_id, + "ComplianceResourceType": compliance_resource_type, + } + return { + "ComplianceType": compliance_type, + "ComplianceResourceId": compliance_resource_id, + "ComplianceResourceType": compliance_resource_type, + "Annotation": annotation, + } + + +def assert_successful_evaluation(test_class, response, resp_expected, evaluations_count=1): + if isinstance(response, dict): + test_class.assertEquals(resp_expected["ComplianceResourceType"], response["ComplianceResourceType"]) + test_class.assertEquals(resp_expected["ComplianceResourceId"], response["ComplianceResourceId"]) + test_class.assertEquals(resp_expected["ComplianceType"], response["ComplianceType"]) + test_class.assertTrue(response["OrderingTimestamp"]) + if "Annotation" in resp_expected or "Annotation" in response: + test_class.assertEquals(resp_expected["Annotation"], response["Annotation"]) + elif isinstance(response, list): + test_class.assertEquals(evaluations_count, len(response)) + for i, response_expected in enumerate(resp_expected): + test_class.assertEquals(response_expected["ComplianceResourceType"], response[i]["ComplianceResourceType"]) + test_class.assertEquals(response_expected["ComplianceResourceId"], response[i]["ComplianceResourceId"]) + test_class.assertEquals(response_expected["ComplianceType"], response[i]["ComplianceType"]) + test_class.assertTrue(response[i]["OrderingTimestamp"]) + if "Annotation" in response_expected or "Annotation" in response[i]: + test_class.assertEquals(response_expected["Annotation"], response[i]["Annotation"]) + + +def assert_customer_error_response(test_class, response, customer_error_code=None, customer_error_message=None): + if customer_error_code: + test_class.assertEqual(customer_error_code, response["customerErrorCode"]) + if customer_error_message: + test_class.assertEqual(customer_error_message, response["customerErrorMessage"]) + test_class.assertTrue(response["customerErrorCode"]) + test_class.assertTrue(response["customerErrorMessage"]) + if "internalErrorMessage" in response: + test_class.assertTrue(response["internalErrorMessage"]) + if "internalErrorDetails" in response: + test_class.assertTrue(response["internalErrorDetails"]) + + +def sts_mock(): + assume_role_response = { + "Credentials": {"AccessKeyId": "string", "SecretAccessKey": "string", "SessionToken": "string"} + } + STS_CLIENT_MOCK.reset_mock(return_value=True) + STS_CLIENT_MOCK.assume_role = MagicMock(return_value=assume_role_response) + + +################## +# Common Testing # +################## + + +class TestStsErrors(unittest.TestCase): + def test_sts_unknown_error(self): + RULE.ASSUME_ROLE_MODE = True + RULE.evaluate_parameters = MagicMock(return_value=True) + STS_CLIENT_MOCK.assume_role = MagicMock( + side_effect=botocore.exceptions.ClientError( + {"Error": {"Code": "unknown-code", "Message": "unknown-message"}}, "operation" + ) + ) + response = RULE.lambda_handler(build_lambda_configurationchange_event("{}"), {}) + assert_customer_error_response(self, response, "InternalError", "InternalError") + + def test_sts_access_denied(self): + RULE.ASSUME_ROLE_MODE = True + RULE.evaluate_parameters = MagicMock(return_value=True) + STS_CLIENT_MOCK.assume_role = MagicMock( + side_effect=botocore.exceptions.ClientError( + {"Error": {"Code": "AccessDenied", "Message": "access-denied"}}, "operation" + ) + ) + response = RULE.lambda_handler(build_lambda_configurationchange_event("{}"), {}) + assert_customer_error_response( + self, response, "AccessDenied", "AWS Config does not have permission to assume the IAM role." + ) diff --git a/rdk/template/runtime/python3.7-lib/rule_test.py b/rdk/template/runtime/python3.7-lib/rule_test.py index 8f11d043..db0cf30c 100644 --- a/rdk/template/runtime/python3.7-lib/rule_test.py +++ b/rdk/template/runtime/python3.7-lib/rule_test.py @@ -4,7 +4,6 @@ import unittest from unittest.mock import patch, MagicMock from botocore.exceptions import ClientError -import rdklib from rdklib import Evaluation, ComplianceType import rdklibtest @@ -20,24 +19,27 @@ # Main Code # ############# -MODULE = __import__("<%RuleName%>") -RULE = MODULE.<%RuleName%>() +MODULE = __import__("check_security_hub_aggregator") +RULE = MODULE.check_security_hub_aggregator() CLIENT_FACTORY = MagicMock() # example for mocking IAM API calls IAM_CLIENT_MOCK = MagicMock() +# STS client for getting account ID +STS_CLIENT_MOCK = MagicMock() def mock_get_client(client_name, *args, **kwargs): if client_name == "iam": return IAM_CLIENT_MOCK + if client_name == "sts": + return STS_CLIENT_MOCK raise Exception("Attempting to create an unknown client") @patch.object(CLIENT_FACTORY, "build_client", MagicMock(side_effect=mock_get_client)) class ComplianceTest(unittest.TestCase): - rule_parameters = { "SomeParameterKey": "SomeParameterValue", "SomeParameterKey2": "SomeParameterValue2", @@ -61,6 +63,7 @@ class ComplianceTest(unittest.TestCase): }, "notificationCreationTime": "2018-07-02T23:05:34.445Z", "messageType": "ConfigurationItemChangeNotification", + "executionRoleArn": "arn:aws:dummy", } list_roles_response = { @@ -69,74 +72,85 @@ class ComplianceTest(unittest.TestCase): "Path": "/", "RoleName": "testrole", "RoleId": "some-role-id", - "Arn": "arn:aws:iam00000056789012:role/testrole", - "CreateDate": datetime(2015, 1, 1), + "Arn": "arn:aws:iam::111111111111:role/testrole", + "CreateDate": datetime.datetime(2015, 1, 1), "Description": "this is a test role", "MaxSessionDuration": 123, "Tags": [ {"Key": "one_tag", "Value": "its_value"}, ], "RoleLastUsed": { - "LastUsedDate": datetime(2015, 1, 1), + "LastUsedDate": datetime.datetime(2015, 1, 1), "Region": "us-east-1", }, }, ] } + test_account_id = "111111111111" + get_caller_identity_response = {"Account": test_account_id} def setUp(self): - pass + STS_CLIENT_MOCK.reset_mock() def test_sample(self): self.assertTrue(True) - # def test_configurationchange_rule(self): - # # Example of how to evaluate a configuration change rule - # response = RULE.evaluate_change( - # event=json.dumps(self.invoking_event_iam_role_sample), - # client_factory=CLIENT_FACTORY, - # configuration_item=self.role_sample_configuration_abridged, - # valid_rule_parameters=json.dumps(self.rule_parameters), - # ) - # resp_expected = [] - # resp_expected.append( - # Evaluation( - # complianceType=ComplianceType.NOT_APPLICABLE, - # annotation="This is a configuration change rule's annotation.", - # resourceId=self.invoking_event_iam_role_sample.get( - # "configurationItem", {} - # ).get("resourceId", None), - # resourceType=RESOURCE_TYPE, - # ) - # ) - # if vars(response[0]) != vars(resp_expected[0]): - # logging.warning(f"Actual response: {vars(response[0])}") - # logging.warning(f"Expected response: {vars(resp_expected[0])}") - # rdklib.assert_successful_evaluation(self, response, resp_expected) - - # def test_periodic_rule(self): - # # Example of how to mock the client response for a list_roles API call - # IAM_CLIENT_MOCK.list_roles = MagicMock(return_value=self.list_roles_response) - # # Example of how to evaluate a periodic rule - # response = RULE.evaluate_periodic( - # event=rdklibtest.create_test_scheduled_event(self.rule_parameters), - # client_factory=CLIENT_FACTORY, - # ) - # resp_expected = [] - # resp_expected.append( - # Evaluation( - # complianceType=ComplianceType.NOT_APPLICABLE, - # resourceId=self.invoking_event_iam_role_sample.get( - # "configurationItem", {} - # ).get("awsAccountId", None), - # resourceType="AWS::::Account", - # annotation="This is a periodic rule's annotation.", - # ) - # ) - # if vars(response[0]) != vars(resp_expected[0]): - # logging.warning(f"Actual response: {vars(response[0])}") - # logging.warning(f"Expected response: {vars(resp_expected[0])}") - # rdklib.assert_successful_evaluation(self, response, resp_expected) + # Example of how to evaluate a configuration change rule + def test_configurationchange_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + response = RULE.evaluate_change( + event=json.dumps(self.invoking_event_iam_role_sample), + client_factory=CLIENT_FACTORY, + configuration_item=self.role_sample_configuration_abridged, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + annotation="This is a configuration change rule's annotation.", + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("resourceId", None), + resourceType=RESOURCE_TYPE, + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) + + # Example of how to mock the client response for a list_roles API call + def test_periodic_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + IAM_CLIENT_MOCK.list_roles = MagicMock(return_value=self.list_roles_response) + # Example of how to evaluate a periodic rule + response = RULE.evaluate_periodic( + event=rdklibtest.create_test_scheduled_event(self.rule_parameters), + client_factory=CLIENT_FACTORY, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("awsAccountId", None), + resourceType="AWS::::Account", + annotation="This is a periodic rule's annotation.", + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) if __name__ == "__main__": diff --git a/rdk/template/runtime/python3.8-lib/rule_test.py b/rdk/template/runtime/python3.8-lib/rule_test.py index 8f11d043..db0cf30c 100644 --- a/rdk/template/runtime/python3.8-lib/rule_test.py +++ b/rdk/template/runtime/python3.8-lib/rule_test.py @@ -4,7 +4,6 @@ import unittest from unittest.mock import patch, MagicMock from botocore.exceptions import ClientError -import rdklib from rdklib import Evaluation, ComplianceType import rdklibtest @@ -20,24 +19,27 @@ # Main Code # ############# -MODULE = __import__("<%RuleName%>") -RULE = MODULE.<%RuleName%>() +MODULE = __import__("check_security_hub_aggregator") +RULE = MODULE.check_security_hub_aggregator() CLIENT_FACTORY = MagicMock() # example for mocking IAM API calls IAM_CLIENT_MOCK = MagicMock() +# STS client for getting account ID +STS_CLIENT_MOCK = MagicMock() def mock_get_client(client_name, *args, **kwargs): if client_name == "iam": return IAM_CLIENT_MOCK + if client_name == "sts": + return STS_CLIENT_MOCK raise Exception("Attempting to create an unknown client") @patch.object(CLIENT_FACTORY, "build_client", MagicMock(side_effect=mock_get_client)) class ComplianceTest(unittest.TestCase): - rule_parameters = { "SomeParameterKey": "SomeParameterValue", "SomeParameterKey2": "SomeParameterValue2", @@ -61,6 +63,7 @@ class ComplianceTest(unittest.TestCase): }, "notificationCreationTime": "2018-07-02T23:05:34.445Z", "messageType": "ConfigurationItemChangeNotification", + "executionRoleArn": "arn:aws:dummy", } list_roles_response = { @@ -69,74 +72,85 @@ class ComplianceTest(unittest.TestCase): "Path": "/", "RoleName": "testrole", "RoleId": "some-role-id", - "Arn": "arn:aws:iam00000056789012:role/testrole", - "CreateDate": datetime(2015, 1, 1), + "Arn": "arn:aws:iam::111111111111:role/testrole", + "CreateDate": datetime.datetime(2015, 1, 1), "Description": "this is a test role", "MaxSessionDuration": 123, "Tags": [ {"Key": "one_tag", "Value": "its_value"}, ], "RoleLastUsed": { - "LastUsedDate": datetime(2015, 1, 1), + "LastUsedDate": datetime.datetime(2015, 1, 1), "Region": "us-east-1", }, }, ] } + test_account_id = "111111111111" + get_caller_identity_response = {"Account": test_account_id} def setUp(self): - pass + STS_CLIENT_MOCK.reset_mock() def test_sample(self): self.assertTrue(True) - # def test_configurationchange_rule(self): - # # Example of how to evaluate a configuration change rule - # response = RULE.evaluate_change( - # event=json.dumps(self.invoking_event_iam_role_sample), - # client_factory=CLIENT_FACTORY, - # configuration_item=self.role_sample_configuration_abridged, - # valid_rule_parameters=json.dumps(self.rule_parameters), - # ) - # resp_expected = [] - # resp_expected.append( - # Evaluation( - # complianceType=ComplianceType.NOT_APPLICABLE, - # annotation="This is a configuration change rule's annotation.", - # resourceId=self.invoking_event_iam_role_sample.get( - # "configurationItem", {} - # ).get("resourceId", None), - # resourceType=RESOURCE_TYPE, - # ) - # ) - # if vars(response[0]) != vars(resp_expected[0]): - # logging.warning(f"Actual response: {vars(response[0])}") - # logging.warning(f"Expected response: {vars(resp_expected[0])}") - # rdklib.assert_successful_evaluation(self, response, resp_expected) - - # def test_periodic_rule(self): - # # Example of how to mock the client response for a list_roles API call - # IAM_CLIENT_MOCK.list_roles = MagicMock(return_value=self.list_roles_response) - # # Example of how to evaluate a periodic rule - # response = RULE.evaluate_periodic( - # event=rdklibtest.create_test_scheduled_event(self.rule_parameters), - # client_factory=CLIENT_FACTORY, - # ) - # resp_expected = [] - # resp_expected.append( - # Evaluation( - # complianceType=ComplianceType.NOT_APPLICABLE, - # resourceId=self.invoking_event_iam_role_sample.get( - # "configurationItem", {} - # ).get("awsAccountId", None), - # resourceType="AWS::::Account", - # annotation="This is a periodic rule's annotation.", - # ) - # ) - # if vars(response[0]) != vars(resp_expected[0]): - # logging.warning(f"Actual response: {vars(response[0])}") - # logging.warning(f"Expected response: {vars(resp_expected[0])}") - # rdklib.assert_successful_evaluation(self, response, resp_expected) + # Example of how to evaluate a configuration change rule + def test_configurationchange_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + response = RULE.evaluate_change( + event=json.dumps(self.invoking_event_iam_role_sample), + client_factory=CLIENT_FACTORY, + configuration_item=self.role_sample_configuration_abridged, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + annotation="This is a configuration change rule's annotation.", + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("resourceId", None), + resourceType=RESOURCE_TYPE, + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) + + # Example of how to mock the client response for a list_roles API call + def test_periodic_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + IAM_CLIENT_MOCK.list_roles = MagicMock(return_value=self.list_roles_response) + # Example of how to evaluate a periodic rule + response = RULE.evaluate_periodic( + event=rdklibtest.create_test_scheduled_event(self.rule_parameters), + client_factory=CLIENT_FACTORY, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("awsAccountId", None), + resourceType="AWS::::Account", + annotation="This is a periodic rule's annotation.", + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) if __name__ == "__main__": diff --git a/rdk/template/runtime/python3.9-lib/rule_test.py b/rdk/template/runtime/python3.9-lib/rule_test.py index 8f11d043..db0cf30c 100644 --- a/rdk/template/runtime/python3.9-lib/rule_test.py +++ b/rdk/template/runtime/python3.9-lib/rule_test.py @@ -4,7 +4,6 @@ import unittest from unittest.mock import patch, MagicMock from botocore.exceptions import ClientError -import rdklib from rdklib import Evaluation, ComplianceType import rdklibtest @@ -20,24 +19,27 @@ # Main Code # ############# -MODULE = __import__("<%RuleName%>") -RULE = MODULE.<%RuleName%>() +MODULE = __import__("check_security_hub_aggregator") +RULE = MODULE.check_security_hub_aggregator() CLIENT_FACTORY = MagicMock() # example for mocking IAM API calls IAM_CLIENT_MOCK = MagicMock() +# STS client for getting account ID +STS_CLIENT_MOCK = MagicMock() def mock_get_client(client_name, *args, **kwargs): if client_name == "iam": return IAM_CLIENT_MOCK + if client_name == "sts": + return STS_CLIENT_MOCK raise Exception("Attempting to create an unknown client") @patch.object(CLIENT_FACTORY, "build_client", MagicMock(side_effect=mock_get_client)) class ComplianceTest(unittest.TestCase): - rule_parameters = { "SomeParameterKey": "SomeParameterValue", "SomeParameterKey2": "SomeParameterValue2", @@ -61,6 +63,7 @@ class ComplianceTest(unittest.TestCase): }, "notificationCreationTime": "2018-07-02T23:05:34.445Z", "messageType": "ConfigurationItemChangeNotification", + "executionRoleArn": "arn:aws:dummy", } list_roles_response = { @@ -69,74 +72,85 @@ class ComplianceTest(unittest.TestCase): "Path": "/", "RoleName": "testrole", "RoleId": "some-role-id", - "Arn": "arn:aws:iam00000056789012:role/testrole", - "CreateDate": datetime(2015, 1, 1), + "Arn": "arn:aws:iam::111111111111:role/testrole", + "CreateDate": datetime.datetime(2015, 1, 1), "Description": "this is a test role", "MaxSessionDuration": 123, "Tags": [ {"Key": "one_tag", "Value": "its_value"}, ], "RoleLastUsed": { - "LastUsedDate": datetime(2015, 1, 1), + "LastUsedDate": datetime.datetime(2015, 1, 1), "Region": "us-east-1", }, }, ] } + test_account_id = "111111111111" + get_caller_identity_response = {"Account": test_account_id} def setUp(self): - pass + STS_CLIENT_MOCK.reset_mock() def test_sample(self): self.assertTrue(True) - # def test_configurationchange_rule(self): - # # Example of how to evaluate a configuration change rule - # response = RULE.evaluate_change( - # event=json.dumps(self.invoking_event_iam_role_sample), - # client_factory=CLIENT_FACTORY, - # configuration_item=self.role_sample_configuration_abridged, - # valid_rule_parameters=json.dumps(self.rule_parameters), - # ) - # resp_expected = [] - # resp_expected.append( - # Evaluation( - # complianceType=ComplianceType.NOT_APPLICABLE, - # annotation="This is a configuration change rule's annotation.", - # resourceId=self.invoking_event_iam_role_sample.get( - # "configurationItem", {} - # ).get("resourceId", None), - # resourceType=RESOURCE_TYPE, - # ) - # ) - # if vars(response[0]) != vars(resp_expected[0]): - # logging.warning(f"Actual response: {vars(response[0])}") - # logging.warning(f"Expected response: {vars(resp_expected[0])}") - # rdklib.assert_successful_evaluation(self, response, resp_expected) - - # def test_periodic_rule(self): - # # Example of how to mock the client response for a list_roles API call - # IAM_CLIENT_MOCK.list_roles = MagicMock(return_value=self.list_roles_response) - # # Example of how to evaluate a periodic rule - # response = RULE.evaluate_periodic( - # event=rdklibtest.create_test_scheduled_event(self.rule_parameters), - # client_factory=CLIENT_FACTORY, - # ) - # resp_expected = [] - # resp_expected.append( - # Evaluation( - # complianceType=ComplianceType.NOT_APPLICABLE, - # resourceId=self.invoking_event_iam_role_sample.get( - # "configurationItem", {} - # ).get("awsAccountId", None), - # resourceType="AWS::::Account", - # annotation="This is a periodic rule's annotation.", - # ) - # ) - # if vars(response[0]) != vars(resp_expected[0]): - # logging.warning(f"Actual response: {vars(response[0])}") - # logging.warning(f"Expected response: {vars(resp_expected[0])}") - # rdklib.assert_successful_evaluation(self, response, resp_expected) + # Example of how to evaluate a configuration change rule + def test_configurationchange_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + response = RULE.evaluate_change( + event=json.dumps(self.invoking_event_iam_role_sample), + client_factory=CLIENT_FACTORY, + configuration_item=self.role_sample_configuration_abridged, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + annotation="This is a configuration change rule's annotation.", + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("resourceId", None), + resourceType=RESOURCE_TYPE, + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) + + # Example of how to mock the client response for a list_roles API call + def test_periodic_rule(self): + # Mock any usage of get_caller_identity + STS_CLIENT_MOCK.get_caller_identity = MagicMock( + return_value=self.get_caller_identity_response + ) + IAM_CLIENT_MOCK.list_roles = MagicMock(return_value=self.list_roles_response) + # Example of how to evaluate a periodic rule + response = RULE.evaluate_periodic( + event=rdklibtest.create_test_scheduled_event(self.rule_parameters), + client_factory=CLIENT_FACTORY, + valid_rule_parameters=json.dumps(self.rule_parameters), + ) + resp_expected = [] + resp_expected.append( + Evaluation( + complianceType=ComplianceType.NOT_APPLICABLE, + resourceId=self.invoking_event_iam_role_sample.get( + "configurationItem", {} + ).get("awsAccountId", None), + resourceType="AWS::::Account", + annotation="This is a periodic rule's annotation.", + ) + ) + if vars(response[0]) != vars(resp_expected[0]): + logging.warning(f"Actual response: {vars(response[0])}") + logging.warning(f"Expected response: {vars(resp_expected[0])}") + rdklibtest.assert_successful_evaluation(self, response, resp_expected) if __name__ == "__main__": diff --git a/rdk/template/terraform/0.11/config_rule.tf b/rdk/template/terraform/0.11/config_rule.tf index 4df8f241..6e190217 100644 --- a/rdk/template/terraform/0.11/config_rule.tf +++ b/rdk/template/terraform/0.11/config_rule.tf @@ -9,13 +9,6 @@ data "aws_iam_policy" "read_only_access" { } data "aws_iam_policy_document" "config_iam_policy" { - - statement{ - actions=["s3:GetObject"] - resources =["arn:${data.aws_partition.current.partition}:s3:::${var.source_bucket}/${var.rule_name}.zip"] - effect = "Allow" - sid= "1" - } statement{ actions=[ "logs:CreateLogGroup", @@ -36,7 +29,6 @@ data "aws_iam_policy_document" "config_iam_policy" { statement{ actions=[ "iam:List*", - "iam:Describe*", "iam:Get*" ] resources = ["*"] diff --git a/rdk/template/terraform/0.12/config_rule.tf b/rdk/template/terraform/0.12/config_rule.tf index 13e2b6f6..ab384bdc 100644 --- a/rdk/template/terraform/0.12/config_rule.tf +++ b/rdk/template/terraform/0.12/config_rule.tf @@ -10,12 +10,6 @@ data "aws_iam_policy" "read_only_access" { data "aws_iam_policy_document" "config_iam_policy" { - statement{ - actions=["s3:GetObject"] - resources = [format("arn:%s:s3:::%s/%s",data.aws_partition.current.partition,var.source_bucket,local.rule_name_source)] - effect = "Allow" - sid= "1" - } statement{ actions=[ "logs:CreateLogGroup", @@ -36,7 +30,6 @@ data "aws_iam_policy_document" "config_iam_policy" { statement{ actions=[ "iam:List*", - "iam:Describe*", "iam:Get*" ] resources = ["*"] diff --git a/testing/linux-python3-buildspec.yaml b/testing/linux-python3-buildspec.yaml index 74930af5..715d06ac 100644 --- a/testing/linux-python3-buildspec.yaml +++ b/testing/linux-python3-buildspec.yaml @@ -14,7 +14,7 @@ phases: commands: - rdk create-region-set -o test-region - rdk -f test-region.yaml init - - rdk create MFA_ENABLED_RULE --runtime python3.8 --resource-types AWS::IAM::User + - rdk create MFA_ENABLED_RULE --runtime python3.10 --resource-types AWS::IAM::User - rdk -f test-region.yaml deploy MFA_ENABLED_RULE - sleep 30 - python3 testing/multi_region_execution_test.py @@ -22,22 +22,26 @@ phases: - rdk -f test-region.yaml undeploy --force MFA_ENABLED_RULE - python3 testing/partition_test.py - rdk init --generate-lambda-layer + - rdk create LP3_TestRule_P310_lib --runtime python3.10-lib --resource-types AWS::EC2::SecurityGroup - rdk create LP3_TestRule_P39_lib --runtime python3.9-lib --resource-types AWS::EC2::SecurityGroup - rdk create LP3_TestRule_P38_lib --runtime python3.8-lib --resource-types AWS::EC2::SecurityGroup - rdk create LP3_TestRule_P37_lib --runtime python3.7-lib --resource-types AWS::EC2::SecurityGroup + - rdk -f test-region.yaml deploy LP3_TestRule_P310_lib --generated-lambda-layer - rdk -f test-region.yaml deploy LP3_TestRule_P39_lib --generated-lambda-layer - rdk -f test-region.yaml deploy LP3_TestRule_P38_lib --generated-lambda-layer - rdk -f test-region.yaml deploy LP3_TestRule_P37_lib --generated-lambda-layer + - yes | rdk -f test-region.yaml undeploy LP3_TestRule_P310_lib - yes | rdk -f test-region.yaml undeploy LP3_TestRule_P39_lib - yes | rdk -f test-region.yaml undeploy LP3_TestRule_P38_lib - yes | rdk -f test-region.yaml undeploy LP3_TestRule_P37_lib + - rdk create LP3_TestRule_P310 --runtime python3.10 --resource-types AWS::EC2::SecurityGroup - rdk create LP3_TestRule_P39 --runtime python3.9 --resource-types AWS::EC2::SecurityGroup - rdk create LP3_TestRule_P38 --runtime python3.8 --resource-types AWS::EC2::SecurityGroup - rdk create LP3_TestRule_P37 --runtime python3.7 --resource-types AWS::EC2::SecurityGroup - - rdk create LP3_TestRule_P3 --runtime python3.9 --resource-types AWS::EC2::SecurityGroup - - rdk create LP3_TestRule_EFSFS --runtime python3.9 --resource-types AWS::EFS::FileSystem - - rdk create LP3_TestRule_ECSTD --runtime python3.7 --resource-types AWS::ECS::TaskDefinition - - rdk create LP3_TestRule_ECSS --runtime python3.9 --resource-types AWS::ECS::Service + - rdk create LP3_TestRule_P3 --runtime python3.10 --resource-types AWS::EC2::SecurityGroup + - rdk create LP3_TestRule_EFSFS --runtime python3.10 --resource-types AWS::EFS::FileSystem + - rdk create LP3_TestRule_ECSTD --runtime python3.10 --resource-types AWS::ECS::TaskDefinition + - rdk create LP3_TestRule_ECSS --runtime python3.10 --resource-types AWS::ECS::Service - rdk modify LP3_TestRule_P3 --input-parameters '{"TestParameter":"TestValue"}' - rdk create LP3_TestRule_P37_Periodic --runtime python3.7 --maximum-frequency One_Hour - rdk create LP3_TestRule_P37lib_Periodic --runtime python3.7-lib --maximum-frequency One_Hour @@ -45,6 +49,8 @@ phases: - rdk create LP3_TestRule_P38lib_Periodic --runtime python3.8-lib --maximum-frequency One_Hour - rdk create LP3_TestRule_P39_Periodic --runtime python3.9 --maximum-frequency One_Hour - rdk create LP3_TestRule_P39lib_Periodic --runtime python3.9-lib --maximum-frequency One_Hour + - rdk create LP3_TestRule_P310_Periodic --runtime python3.10 --maximum-frequency One_Hour + - rdk create LP3_TestRule_P310lib_Periodic --runtime python3.10-lib --maximum-frequency One_Hour - rdk test-local --all - rdk deploy --all - yes | rdk undeploy LP3_TestRule_P3 @@ -54,6 +60,8 @@ phases: - yes | rdk undeploy LP3_TestRule_P38_Periodic - yes | rdk undeploy LP3_TestRule_P39 - yes | rdk undeploy LP3_TestRule_P39_Periodic + - yes | rdk undeploy LP3_TestRule_P310 + - yes | rdk undeploy LP3_TestRule_P310_Periodic - sleep 30 - rdk logs LP3_TestRule_P3 - yes | rdk undeploy -a diff --git a/testing/windows-python2-buildspec.yaml b/testing/windows-python2-buildspec.yaml deleted file mode 100644 index 57dd3042..00000000 --- a/testing/windows-python2-buildspec.yaml +++ /dev/null @@ -1,13 +0,0 @@ -version: 0.1 - -phases: - install: - commands: - - apt-get update -y - build: - commands: - - echo Creating Windows build server and running tests - - bash testing/test_windows.sh 2 - post_build: - commands: - - echo Build completed on `date` diff --git a/testing/windows-python3-buildspec.yaml b/testing/windows-python3-buildspec.yaml index 4e385fd6..5c92b963 100644 --- a/testing/windows-python3-buildspec.yaml +++ b/testing/windows-python3-buildspec.yaml @@ -14,35 +14,41 @@ phases: commands: - rdk create-region-set -o test-region - rdk -f test-region.yaml init - - rdk create W_MFA_ENABLED_RULE --runtime python3.8 --resource-types AWS::IAM::User + - rdk create W_MFA_ENABLED_RULE --runtime python3.10 --resource-types AWS::IAM::User - rdk -f test-region.yaml deploy W_MFA_ENABLED_RULE - python testing/win_multi_region_execution_test.py - rdk -f test-region.yaml undeploy --force W_MFA_ENABLED_RULE - python testing/win_partition_test.py - rdk init --generate-lambda-layer + - rdk create WP3_TestRule_P310_lib --runtime python3.10-lib --resource-types AWS::EC2::SecurityGroup - rdk create WP3_TestRule_P39_lib --runtime python3.9-lib --resource-types AWS::EC2::SecurityGroup - rdk create WP3_TestRule_P38_lib --runtime python3.8-lib --resource-types AWS::EC2::SecurityGroup - rdk create WP3_TestRule_P37_lib --runtime python3.7-lib --resource-types AWS::EC2::SecurityGroup + - rdk -f test-region.yaml deploy WP3_TestRule_P310_lib --generated-lambda-layer - rdk -f test-region.yaml deploy WP3_TestRule_P39_lib --generated-lambda-layer - rdk -f test-region.yaml deploy WP3_TestRule_P38_lib --generated-lambda-layer - rdk -f test-region.yaml deploy WP3_TestRule_P37_lib --generated-lambda-layer + - rdk -f test-region.yaml undeploy WP3_TestRule_P310_lib --force - rdk -f test-region.yaml undeploy WP3_TestRule_P39_lib --force - rdk -f test-region.yaml undeploy WP3_TestRule_P38_lib --force - rdk -f test-region.yaml undeploy WP3_TestRule_P37_lib --force + - rdk create WP3_TestRule_P310 --runtime python3.10 --resource-types AWS::EC2::SecurityGroup - rdk create WP3_TestRule_P39 --runtime python3.9 --resource-types AWS::EC2::SecurityGroup - rdk create WP3_TestRule_P38 --runtime python3.8 --resource-types AWS::EC2::SecurityGroup - rdk create WP3_TestRule_P37 --runtime python3.7 --resource-types AWS::EC2::SecurityGroup - - rdk create WP3_TestRule_P3 --runtime python3.9 --resource-types AWS::EC2::SecurityGroup - - rdk create WP3_TestRule_EFSFS --runtime python3.9 --resource-types AWS::EFS::FileSystem - - rdk create WP3_TestRule_ECSTD --runtime python3.7 --resource-types AWS::ECS::TaskDefinition - - rdk create WP3_TestRule_ECSS --runtime python3.9 --resource-types AWS::ECS::Service - - rdk modify WP3_TestRule_P3 --runtime python3.8 + - rdk create WP3_TestRule_P3 --runtime python3.10 --resource-types AWS::EC2::SecurityGroup + - rdk create WP3_TestRule_EFSFS --runtime python3.10 --resource-types AWS::EFS::FileSystem + - rdk create WP3_TestRule_ECSTD --runtime python3.10 --resource-types AWS::ECS::TaskDefinition + - rdk create WP3_TestRule_ECSS --runtime python3.10 --resource-types AWS::ECS::Service + - rdk modify WP3_TestRule_P3 --runtime python3.10 - rdk create WP3_TestRule_P37_Periodic --runtime python3.7 --maximum-frequency One_Hour - rdk create WP3_TestRule_P37lib_Periodic --runtime python3.7-lib --maximum-frequency One_Hour - rdk create WP3_TestRule_P38_Periodic --runtime python3.8 --maximum-frequency One_Hour - rdk create WP3_TestRule_P38lib_Periodic --runtime python3.8-lib --maximum-frequency One_Hour - rdk create WP3_TestRule_P39_Periodic --runtime python3.9 --maximum-frequency One_Hour - rdk create WP3_TestRule_P39lib_Periodic --runtime python3.9-lib --maximum-frequency One_Hour + - rdk create WP3_TestRule_P310_Periodic --runtime python3.10 --maximum-frequency One_Hour + - rdk create WP3_TestRule_P310lib_Periodic --runtime python3.10-lib --maximum-frequency One_Hour - rdk test-local --all - rdk deploy --all - rdk undeploy WP3_TestRule_P3 --force @@ -51,7 +57,9 @@ phases: - rdk undeploy WP3_TestRule_P38 --force - rdk undeploy WP3_TestRule_P38_Periodic --force - rdk undeploy WP3_TestRule_P39 --force - - rdk undeploy WP3_TestRule_P39_Periodic --force + - rdk undeploy WP3_TestRule_P39_Periodic --force + - rdk undeploy WP3_TestRule_P310 --force + - rdk undeploy WP3_TestRule_P310_Periodic --force - rdk logs WP3_TestRule_P3 - rdk undeploy -a --force post_build: