From 0ed8427a6aa91670746f19129547f17f7c717471 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Fri, 18 Feb 2022 21:51:52 +0530 Subject: [PATCH] Release/v1.10.0 (#121) --- README.md | 3 + .../jobs/aws_ec2_close_port_11211/README.md | 53 ++++ .../jobs/aws_ec2_close_port_11211/__init__.py | 0 .../aws_ec2_close_port_11211.py | 131 ++++++++++ .../aws_ec2_close_port_11211/constraints.txt | 43 ++++ .../minimum_policy.json | 15 ++ .../requirements-dev.txt | 6 + .../aws_ec2_close_port_11211/requirements.txt | 6 + .../README.md | 74 ++++++ .../__init__.py | 0 .../aws_rds_snapshot_remove_publicaccess.py | 159 ++++++++++++ .../constraints.txt | 43 ++++ .../minimum_policy.json | 14 ++ .../requirements-dev.txt | 6 + .../requirements.txt | 6 + .../README.md | 68 +++++ .../__init__.py | 0 ...s3_remove_fullaccess_authenticatedusers.py | 103 ++++++++ .../constraints.txt | 43 ++++ .../minimum_policy.json | 14 ++ .../requirements-dev.txt | 6 + .../requirements.txt | 6 + test/unit/test_aws_ec2_close_port_11211.py | 39 +++ ...st_aws_rds_snapshot_remove_publicaccess.py | 78 ++++++ ...s3_remove_fullaccess_authenticatedusers.py | 232 ++++++++++++++++++ tox.ini | 18 ++ 26 files changed, 1166 insertions(+) create mode 100644 remediation_worker/jobs/aws_ec2_close_port_11211/README.md create mode 100644 remediation_worker/jobs/aws_ec2_close_port_11211/__init__.py create mode 100644 remediation_worker/jobs/aws_ec2_close_port_11211/aws_ec2_close_port_11211.py create mode 100644 remediation_worker/jobs/aws_ec2_close_port_11211/constraints.txt create mode 100644 remediation_worker/jobs/aws_ec2_close_port_11211/minimum_policy.json create mode 100644 remediation_worker/jobs/aws_ec2_close_port_11211/requirements-dev.txt create mode 100644 remediation_worker/jobs/aws_ec2_close_port_11211/requirements.txt create mode 100644 remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/README.md create mode 100644 remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/__init__.py create mode 100644 remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/aws_rds_snapshot_remove_publicaccess.py create mode 100644 remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/constraints.txt create mode 100644 remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/minimum_policy.json create mode 100644 remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements-dev.txt create mode 100644 remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements.txt create mode 100644 remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/README.md create mode 100644 remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/__init__.py create mode 100644 remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/aws_s3_remove_fullaccess_authenticatedusers.py create mode 100644 remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/constraints.txt create mode 100644 remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/minimum_policy.json create mode 100644 remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements-dev.txt create mode 100644 remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements.txt create mode 100755 test/unit/test_aws_ec2_close_port_11211.py create mode 100644 test/unit/test_aws_rds_snapshot_remove_publicaccess.py create mode 100644 test/unit/test_aws_s3_remove_fullaccess_authenticatedusers.py diff --git a/README.md b/README.md index fffc546..398ff2f 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,9 @@ The table below lists all the supported jobs with their links. | 40. | 5c8c260b7a550e1fb6560bf4 | IAM password policy should set a minimum length | [aws-iam-password-policy-min-length](remediation_worker/jobs/aws_iam_password_policy_min_length) | | 41. | 5c8c26107a550e1fb6560bfc | IAM password policy should prevent password reuse | [aws-iam-password-reuse-prevention](remediation_worker/jobs/aws_iam_password_reuse_prevention) | | 42. | 7fe4eb28-3b82-11eb-adc1-0242ac120002 | IAM server certificates that are expired should be removed | [aws-iam-server-certificate-expired](remediation_worker/jobs/aws_iam_server_certificate_expired) | +| 43. | bd9d77b6-635d-4e06-9760-8957d8eaeb38 | EC2 instance should restrict public access to Memcache UDP port (11211) | [aws_ec2_close_port_11211](remediation_worker/jobs/aws_ec2_close_port_11211) | +| 44. | 5c8c26487a550e1fb6560c4a | RDS snapshot should restrict public access | [aws-rds-snapshot-remove-publicaccess](remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess) | +| 45. | 5c8c26567a550e1fb6560c5d | S3 bucket should not give full access to all authenticated users | [aws_s3_remove_fullaccess_authenticatedusers](remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers) | ## Contributing The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). diff --git a/remediation_worker/jobs/aws_ec2_close_port_11211/README.md b/remediation_worker/jobs/aws_ec2_close_port_11211/README.md new file mode 100644 index 0000000..6db7d89 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_close_port_11211/README.md @@ -0,0 +1,53 @@ +# Close Port 11211 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 11211 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 11211 in the port range and source as "0.0.0.0/0" or "::/0". + +### Applicable Rule + +##### Rule ID: +bd9d77b6-635d-4e06-9760-8957d8eaeb38 + +##### Rule Name: +EC2 instance should restrict public access to Memcache UDP port (11211) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances`, `ec2:RevokeSecurityGroupIngress`, `ec2:DescribeSecurityGroupRules`. + +You may find the latest example policy file [here](minimum_policy.json) + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r ../../requirements.txt + python3 aws_ec2_close_port_11211.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + python3 -m pytest +``` + +## Contributing +The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/aws_ec2_close_port_11211/__init__.py b/remediation_worker/jobs/aws_ec2_close_port_11211/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/aws_ec2_close_port_11211/aws_ec2_close_port_11211.py b/remediation_worker/jobs/aws_ec2_close_port_11211/aws_ec2_close_port_11211.py new file mode 100644 index 0000000..89c5578 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_close_port_11211/aws_ec2_close_port_11211.py @@ -0,0 +1,131 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +from __future__ import annotations +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort11211(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: Exception, JSONDecodeError + """ + remediation_entry = json.loads(payload) + notification_info = remediation_entry.get("notificationInfo", None) + + finding_info = notification_info.get("FindingInfo", None) + instance_id = finding_info.get("ObjectId", None) + + if instance_id is None: + logging.error("Missing parameters for 'payload.notificationInfo.ObjectId'.") + raise Exception( + "Missing parameters for 'payload.notificationInfo.ObjectId'." + ) + + region = finding_info.get("Region", None) + if region is None: + logging.warning("no region specified - defaulting to us-east-1") + region = "us-east-1" + + logging.info("parsed params") + logging.info(f" instance_id: {instance_id}") + logging.info(f" region: {region}") + + return {"instance_id": instance_id}, region + + def remediate(self, client, instance_id): + """Block public access to port 11211 of all security groups attached to an EC2 instance + by removing all the rules with port 11211 in the port range + :param client: Instance of the AWS boto3 client. + :param instance_id: The ID of the EC2 instance. + :type instance_id: str + :returns: Integer signaling success or failure + :rtype: int + :raises: botocore.exceptions.ClientError + """ + try: + port = 11211 + logging.info(" executing client.describe_instances") + logging.info(f" InstanceId: {instance_id}") + # Extract security group Id + security_groups = client.describe_instances(InstanceIds=[instance_id])[ + "Reservations" + ][0]["Instances"][0]["SecurityGroups"] + for sg_info in security_groups: + security_group_id = sg_info["GroupId"] + logging.info(" executing client.describe_security_group_rules") + logging.info(f" group-id: {security_group_id}") + # List all the security group rules + security_group_rules = client.describe_security_group_rules( + Filters=[{"Name": "group-id", "Values": [security_group_id]},], + MaxResults=1000, + ) + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "udp" + and rule["IsEgress"] is False + and rule["FromPort"] <= port + and rule["ToPort"] >= port + and ( + ("CidrIpv4" in rule and rule["CidrIpv4"] == "0.0.0.0/0") + or ("CidrIpv6" in rule and rule["CidrIpv6"] == "::/0") + ) + ): + # Removes Ingress security group rule containing port 11211 in the range with + # protocol 'tcp', source '0.0.0.0/0' or '::/0' + logging.info( + " executing client.revoke_security_group_ingress" + ) + logging.info(f" GroupId: {security_group_id}") + logging.info( + f" SecurityGroupRuleIds: {rule['SecurityGroupRuleId']}" + ) + client.revoke_security_group_ingress( + GroupId=security_group_id, + SecurityGroupRuleIds=[rule["SecurityGroupRuleId"]], + ) + logging.info("successfully executed remediation") + except Exception as e: + logging.error(f"{str(e)}") + return 0 + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params, region = self.parse(args[1]) + client = boto3.client("ec2", region_name=region) + logging.info("acquired ec2 client and parsed params - starting remediation") + rc = self.remediate(client=client, **params) + return rc + + +if __name__ == "__main__": + logging.info(f"{sys.argv[0]} called - running now") + obj = EC2ClosePort11211() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/aws_ec2_close_port_11211/constraints.txt b/remediation_worker/jobs/aws_ec2_close_port_11211/constraints.txt new file mode 100644 index 0000000..0d9b3c2 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_close_port_11211/constraints.txt @@ -0,0 +1,43 @@ +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/aws_ec2_close_port_11211/minimum_policy.json b/remediation_worker/jobs/aws_ec2_close_port_11211/minimum_policy.json new file mode 100644 index 0000000..66a5311 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_close_port_11211/minimum_policy.json @@ -0,0 +1,15 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort11211", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/aws_ec2_close_port_11211/requirements-dev.txt b/remediation_worker/jobs/aws_ec2_close_port_11211/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_close_port_11211/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/aws_ec2_close_port_11211/requirements.txt b/remediation_worker/jobs/aws_ec2_close_port_11211/requirements.txt new file mode 100644 index 0000000..93f7845 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_close_port_11211/requirements.txt @@ -0,0 +1,6 @@ +boto3==1.18.4 \ + --hash=sha256:649ed1ca205f5ee0b0328d54580780aebc1a7a05681a24f6ee05253007ca48d8 \ + --hash=sha256:7079b40bd6621c54a0385a8fc11240cff4318a4d487292653e393e18254f5d94 +botocore==1.21.5 \ + --hash=sha256:0070c5e02b581db40ff5fd1b5e02db90ed88e7e861901894bd78fd998656da68 \ + --hash=sha256:bed34fe7a007180f4208b65515bab1755cdd9fcf2c6720f74ae7ecd2e707f4b7 diff --git a/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/README.md b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/README.md new file mode 100644 index 0000000..a6db3f4 --- /dev/null +++ b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/README.md @@ -0,0 +1,74 @@ +# Disable public access to RDS Snapshots + +This job removes public access from RDS snapshots and makes the snapshots private. + +## Getting Started + +##### Rule ID: +5c8c26487a550e1fb6560c4a + +##### Rule Name: +RDS snapshot should restrict public access + +### Prerequisites + +The provided AWS credential must have permissions that listed in the policy file [here](minimum_policy.json) + +### Running the script +You may run this script using following commands: +```shell script + pip install -r ../../requirements.txt + python3 aws_rds_snapshot_remove_publicaccess.py "`cat finding.json`" +``` + where finding.json has snapshotid id and region info: +```json + { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "database-1", + "Region": "us-west-2" + } + } +} +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + python3 -m pytest test +``` + +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/__init__.py b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/aws_rds_snapshot_remove_publicaccess.py b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/aws_rds_snapshot_remove_publicaccess.py new file mode 100644 index 0000000..764214b --- /dev/null +++ b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/aws_rds_snapshot_remove_publicaccess.py @@ -0,0 +1,159 @@ +# Copyright (c) 2021 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +from __future__ import annotations + +import json +import logging +import sys + +import boto3 +from botocore.exceptions import ClientError + +logging.basicConfig(level=logging.INFO) + + +class RDSSnapShotRemovePublicAccess(): + def parse(self, payload): + """Parse payload received from Remediation Service. + + Args: + payload: (str) JSON string containing parameters + received from the remediation service. + + Raises: + Exception: JSONDecodeError + + Returns: + (dict) Dictionary of parsed parameters. + """ + remediation_entry = json.loads(payload) + notification_info = remediation_entry.get("notificationInfo", None) + finding_info = notification_info.get("FindingInfo", None) + + snapshot_identifier = finding_info.get("ObjectId", None) + region = finding_info.get("Region", None) + + if snapshot_identifier is None: + logging.error("Missing parameters for 'RDS SNAPSHOT IDENTIFIER'.") + raise Exception("Missing parameters for 'RDS SNAPSHOT IDENTIFIER'.") + + if region is None: + logging.error("Missing parameters for 'REGION'.") + raise Exception("Missing parameters for 'REGION'.") + + logging.info("parsed params") + logging.info(" rds snapshot identifier: %s", snapshot_identifier) + logging.info(" region: %s", region) + + return { + "instance_id": snapshot_identifier, + "region": region + } + + def remediate(self, client, snapshot_identifier): + """ + Removes public access from RDS snapshots + and makes the snapshots share private. + + Args: + snapshot_identifier ([str]): The identifier of the RDS snapshot. + client: Instance of the AWS boto3 client. + """ + + logging.info("Beginning RDS snapshot share public evaluation.") + + # Check to see if the database snapshot + # is shared publicly + logging.info("executing client.describe_db_snapshot_attributes") + logging.info(f"RDS instance={snapshot_identifier}") + snapshot_attrs_result = client.describe_db_snapshot_attributes(DBSnapshotIdentifier=snapshot_identifier).get('DBSnapshotAttributesResult') + snapshot_attrs = snapshot_attrs_result.get("DBSnapshotAttributes") + snapshot_restore_attr = next(snapshot_attr for snapshot_attr in snapshot_attrs if snapshot_attr["AttributeName"] == "restore") + is_public = True if "all" in snapshot_restore_attr["AttributeValues"] else False + # If it is public, set to private + if is_public: + logging.info( + "RDS snapshot %s is public, removing public access...", + snapshot_identifier + ) + try: + logging.info("executing client.modify_db_snapshot_attribute and apply immediately") + logging.info("Attribute=Restore,Remove all AttributeValue") + client.modify_db_snapshot_attribute( + DBSnapshotIdentifier=snapshot_identifier, + AttributeName="restore",ValuesToRemove=["all"] + ) + logging.info( + "RDS snapshot %s is now private.", + snapshot_identifier + ) + except ClientError as state_err: + # If the remediation is run during maintenance + # or a creation/modification, inform it is not in the right state + # user should retry. + # This is b/c you can describe a bad instance state + # with no errors, but not modify. + # Otherwise, we would catch this error + # on the "describe_db_snapshot_attributes" call + if state_err.response["Error"]["Code"] == "InvalidDBSnapshotState": + logging.info( + "RDS database snapshot %s is in an unavailable state. Waiting..", + snapshot_identifier + ) + + logging.error( + "Remediation RDS database snapshot %s failed", + snapshot_identifier + ) + return 1 + except Exception as e: + error = "Receiving other exceptions {0} while setting RDS database snapshot {1} to private".format(str(e), snapshot_identifier) + logging.error(error) + return 1 + + else: + logging.info( + "RDS database snapshot %s is private, taking no action.", + snapshot_identifier + ) + return 0 + + def run(self, args): + """ + Run the remediation job. + + Args: + args ([list]): List of arguments provided to the job. + + Returns: + [int] + """ + params = self.parse(args[1]) + client = boto3.client("rds", region_name=params['region']) + + logging.info( + "acquired rds client and parsed params - starting remediation" + ) + + return self.remediate(client, params['instance_id']) + + +if __name__ == "__main__": + logging.info( + "aws_rds_snapshot_remove_public_access.py called - running now %s", + sys.argv[0] + ) + obj = RDSSnapShotRemovePublicAccess() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/constraints.txt b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/constraints.txt @@ -0,0 +1,43 @@ +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +s3transfer==0.3.3 \ + --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ + --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/minimum_policy.json b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/minimum_policy.json new file mode 100644 index 0000000..5569777 --- /dev/null +++ b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "RDSPermissions", + "Effect": "Allow", + "Action": [ + "rds:DescribeDBInstances", + "rds:ModifyDBInstance" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements-dev.txt b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements.txt b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements.txt @@ -0,0 +1,6 @@ +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/README.md b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/README.md new file mode 100644 index 0000000..e3261f0 --- /dev/null +++ b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/README.md @@ -0,0 +1,68 @@ +# Remove S3 Public Admin ACL + +This job will remove the public "full access privileges" permission. All other ACL permissions will be left alone. This means that if "AuthenticatedUsers" have access to FULL_CONTROL, the permissions will be changed to remove the 'FULL_CONTROL' permission. + +### Applicable Rule + +##### Rule ID: +5c8c26567a550e1fb6560c5d + +##### Rule Name: +S3 bucket should not give full access to all authenticated users + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `s3:GetBucketAcl` and `s3:PutBucketAcl` + +You may find the latest example policy file [here](minimum_policy.json) + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r ../../requirements.txt + python3 aws_s3_remove_fullaccess_authenticatedusers.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + python3 -m pytest test +``` + +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/__init__.py b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/aws_s3_remove_fullaccess_authenticatedusers.py b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/aws_s3_remove_fullaccess_authenticatedusers.py new file mode 100644 index 0000000..e671afe --- /dev/null +++ b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/aws_s3_remove_fullaccess_authenticatedusers.py @@ -0,0 +1,103 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +from __future__ import annotations + +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class S3RemoveFullAccessAuthUsers: + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters sent to the remediation job. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: Exception, JSONDecodeError + """ + + remediation_entry = json.loads(payload) + notification_info = remediation_entry.get("notificationInfo", None) + finding_info = notification_info.get("FindingInfo", None) + bucket_name = finding_info.get("ObjectId", None) + + if bucket_name is None: + logging.error("Missing parameters for 'BUCKET_NAME'.") + raise Exception("Missing parameters for 'BUCKET_NAME'.") + + logging.info("parsed params") + logging.info(f" bucket_name: {bucket_name}") + + return {"bucket_name": bucket_name} + + def remediate(self, client, bucket_name): + """Block public access ACL to authenticated users + + :param client: Instance of the AWS boto3 client. + :param bucket_name: The name of the bucket for which to block access. + :type bucket_name: str. + :returns: Integer signaling success or failure + :rtype: int + """ + + logging.info("making api call to client.get_bucket_acl") + try: + bucket_acl = client.get_bucket_acl(Bucket=bucket_name) + new_grants = [] + for grant in bucket_acl["Grants"]: + if "URI" in grant["Grantee"]: + if grant["Grantee"][ + "URI"] == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" and ( + grant["Permission"] == "FULL_CONTROL"): + logging.info( + "found public full_control grant - excluding it from the new list of grants" + ) + else: + new_grants.append(grant) + + acl_policy = {"Grants": new_grants, "Owner": bucket_acl["Owner"]} + logging.info("making api call to client.put_bucket_acl") + client.put_bucket_acl(AccessControlPolicy=acl_policy, Bucket=bucket_name) + logging.info(f"successfully executed remediation for bucket: {bucket_name}") + except Exception as e: + error = "Receiving other exceptions {0} while excluding full access privilges for authenticated users for the s3 bucket {1}".format(str(e), bucket_name) + logging.error(error) + return 1 + return 0 + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + client = boto3.client("s3") + params = self.parse(args[1]) + logging.info("acquired s3 client and parsed params - starting remediation") + rc = self.remediate(client=client, **params) + return rc + + +if __name__ == "__main__": + logging.info("aws_s3_remove_fullaccess_authenticatedusers.py called - running now") + obj = S3RemoveFullAccessAuthUsers() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/constraints.txt b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/constraints.txt @@ -0,0 +1,43 @@ +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +s3transfer==0.3.3 \ + --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ + --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/minimum_policy.json b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/minimum_policy.json new file mode 100644 index 0000000..28627a7 --- /dev/null +++ b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "S3RemovePublicAdminACL", + "Effect": "Allow", + "Action": [ + "s3:GetBucketAcl", + "s3:PutBucketAcl" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements-dev.txt b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements.txt b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements.txt @@ -0,0 +1,6 @@ +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/test/unit/test_aws_ec2_close_port_11211.py b/test/unit/test_aws_ec2_close_port_11211.py new file mode 100755 index 0000000..730a8e1 --- /dev/null +++ b/test/unit/test_aws_ec2_close_port_11211.py @@ -0,0 +1,39 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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 pytest + +from remediation_worker.jobs.aws_ec2_close_port_11211.aws_ec2_close_port_11211 import EC2ClosePort11211 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort11211: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort11211() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_aws_rds_snapshot_remove_publicaccess.py b/test/unit/test_aws_rds_snapshot_remove_publicaccess.py new file mode 100644 index 0000000..a2558f4 --- /dev/null +++ b/test/unit/test_aws_rds_snapshot_remove_publicaccess.py @@ -0,0 +1,78 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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 pytest +#\from botocore.exceptions import ClientError + +from remediation_worker.jobs.aws_rds_snapshot_remove_publicaccess.aws_rds_snapshot_remove_publicaccess import RDSSnapShotRemovePublicAccess + + +@pytest.fixture +def valid_payload(): + return """ { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } + } +""" + + +class TestRDSRemovePublicEndpoint: + def test_parse_payload(self, valid_payload): + obj = RDSSnapShotRemovePublicAccess() + param = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" + assert "region" in param + assert param["region"] == "us-east-1" + + def test_remediation_success(self, valid_payload): + class TestClient(object): + def modify_db_snapshot_attribute(self, **kwargs): + return 1 + + def describe_db_snapshot_attributes(self, **kwargs): + rds_snapshot = {'DBSnapshotAttributesResult': { + 'DBSnapshotAttributes': [{'AttributeName': 'restore', 'AttributeValues': ['all']}]}} + return rds_snapshot + + client = TestClient() + obj = RDSSnapShotRemovePublicAccess() + assert obj.remediate(client, "database-1") == 0 + + + def test_remediation_not_success(self, valid_payload): + class TestClient(object): + def modify_db_snapshot_attribute(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "InvalidDBSnapshotState", + "Message": "InvalidDBSnapshotState msg", + } + }, + "InvalidDBSnapshot", + ) + + def describe_db_snapshot_attributes(self, **kwargs): + rds_snapshot = {'DBSnapshotAttributesResult': { + 'DBSnapshotAttributes': [{'AttributeName': 'restore', 'AttributeValues': ['all']}]}} + return rds_snapshot + + client = TestClient() + obj = RDSSnapShotRemovePublicAccess() + assert obj.remediate(client, "database-1") == 1 diff --git a/test/unit/test_aws_s3_remove_fullaccess_authenticatedusers.py b/test/unit/test_aws_s3_remove_fullaccess_authenticatedusers.py new file mode 100644 index 0000000..2986d55 --- /dev/null +++ b/test/unit/test_aws_s3_remove_fullaccess_authenticatedusers.py @@ -0,0 +1,232 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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 pytest +from botocore.exceptions import ClientError + +from remediation_worker.jobs.aws_s3_remove_fullaccess_authenticatedusers.aws_s3_remove_fullaccess_authenticatedusers import ( + S3RemoveFullAccessAuthUsers, +) + + +@pytest.fixture +def valid_payload1(): + return """ + { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "foo" + } + } + } + """ + + +@pytest.fixture +def invalid_payload(): + return """ + { + "notificationInfo": { + "FindingInfo": { + "None": "foo" + } + } + } + """ + +class TestS3RemoveFullAccessAuthUsers (object): + def test_parse_payload_success(self, valid_payload1): + obj = S3RemoveFullAccessAuthUsers() + result = obj.parse(valid_payload1) + assert "bucket_name" in result + + def test_parse_payload_with_missing_param(self, invalid_payload): + obj = S3RemoveFullAccessAuthUsers() + with pytest.raises(Exception): + assert obj.parse(invalid_payload) + + def test_remediate_success(self): + class TestClient(object): + def put_public_access_block(self, **kwargs): + return None + + def put_bucket_acl(self, **kwargs): + return None + + def get_bucket_acl(self, **kwargs): + return { + "Owner": {"DisplayName": "someownerid", "ID": "alongid"}, + "Grants": [ + { + "Grantee": { + "DisplayName": "displaynameagain", + "ID": "someid", + "Type": "CanonicalUser", + }, + "Permission": "FULL_CONTROL", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "WRITE", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "READ_ACP", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + }, + "Permission": "READ", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + }, + "Permission": "WRITE", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers", + }, + "Permission": "FULL_CONTROL", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + }, + "Permission": "WRITE_ACP", + }, + ], + } + + client = TestClient() + action = S3RemoveFullAccessAuthUsers() + assert action.remediate(client, "bucket_name") == 0 + + def test_remediate_success_full_control(self): + class TestClient(object): + def put_public_access_block(self, **kwargs): + return None + + def put_bucket_acl(self, **kwargs): + return None + + def get_bucket_acl(self, **kwargs): + return { + "Owner": {"DisplayName": "someownerid", "ID": "alongid"}, + "Grants": [ + { + "Grantee": { + "DisplayName": "displaynameagain", + "ID": "someid", + "Type": "CanonicalUser", + }, + "Permission": "FULL_CONTROL", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "WRITE", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "READ_ACP", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/global/AllUsers", + }, + "Permission": "FULL_CONTROL", + }, + ], + } + + client = TestClient() + action = S3RemoveFullAccessAuthUsers() + assert action.remediate(client, "bucket_name") == 0 + + def test_remediate_success_no_grants(self): + class TestClient(object): + def put_public_access_block(self, **kwargs): + return None + + def put_bucket_acl(self, **kwargs): + return None + + def get_bucket_acl(self, **kwargs): + return { + "Owner": {"DisplayName": "someownerid", "ID": "alongid"}, + "Grants": [ + { + "Grantee": { + "DisplayName": "displaynameagain", + "ID": "someid", + "Type": "CanonicalUser", + }, + "Permission": "FULL_CONTROL", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "WRITE", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "READ_ACP", + }, + ], + } + + client = TestClient() + action = S3RemoveFullAccessAuthUsers() + assert action.remediate(client, "bucket_name") == 0 + + def test_remediate_with_exception(self): + class TestClient(object): + def put_bucket_encryption(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "NotFound", + "Message": "InvalidPermission.NotFound", + } + }, + "TestS3RemovePublicAdminAcl", + ) + + client = TestClient() + action = S3RemoveFullAccessAuthUsers() + assert action.remediate(client, "bucket_name") == 1 diff --git a/tox.ini b/tox.ini index 6b5443e..952a981 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,9 @@ envlist = unit-rds-remove-public-endpoint unit-rds_enable_version_update unit-kinesis-encrypt-stream + unit-aws_ec2_close_port_11211 + unit-aws-rds-snapshot-remove-publicaccess + unit-aws_s3_remove_fullaccess_authenticatedusers [testenv] @@ -414,3 +417,18 @@ deps = -r remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements changedir = test pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_iam_server_certificate_expired.py deps = -r remediation_worker/jobs/aws_iam_server_certificate_expired/requirements-dev.txt + +[testenv:unit-aws-ec2-close-port-11211] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_ec2_close_port_11211.py +deps = -r remediation_worker/jobs/aws_ec2_close_port_11211/requirements-dev.txt + +[testenv:unit-aws-rds-snapshot-remove-publicaccess] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_rds_snapshot_remove_publicaccess.py +deps = -r remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess/requirements-dev.txt + +[testenv:unit-aws_s3_remove_fullaccess_authenticatedusers] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_s3_iam_remove_fullaccess_authenticatedusers.py +deps = -r remediation_worker/jobs/aws_s3_remove_fullaccess_authenticatedusers/requirements-dev.txt