From 9993eaf718e043da4708ac194c10a5a5a173886c Mon Sep 17 00:00:00 2001 From: lytran2000 <44222483+lytran2000@users.noreply.github.com> Date: Fri, 21 May 2021 08:12:20 -0700 Subject: [PATCH 01/23] =?UTF-8?q?Initial=20commit=20for=20ec2=20close=20po?= =?UTF-8?q?rt=20for=201433,=201521,=2020,=2021,=2023,=2027017,=20=E2=80=A6?= =?UTF-8?q?=20(#93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, 3306, 5439, 5601, 8080, 9200 and 9300 * Checking in unit test and tox.ini, made modification to remove common pkg from ec2_close_port_20.py * Checking in README with addition aws close port rules * Update README with correct port names for the new scripts --- README.md | 11 ++ .../jobs/ec2_close_port_1433/README.md | 53 +++++++ .../jobs/ec2_close_port_1433/__init__.py | 0 .../jobs/ec2_close_port_1433/constraints.txt | 43 +++++ .../ec2_close_port_1433.py | 144 +++++++++++++++++ .../ec2_close_port_1433/minimum_policy.json | 14 ++ .../ec2_close_port_1433/requirements-dev.txt | 6 + .../jobs/ec2_close_port_1433/requirements.txt | 6 + .../jobs/ec2_close_port_1521/README.md | 54 +++++++ .../jobs/ec2_close_port_1521/__init__.py | 0 .../jobs/ec2_close_port_1521/constraints.txt | 43 +++++ .../ec2_close_port_1521.py | 144 +++++++++++++++++ .../ec2_close_port_1521/minimum_policy.json | 14 ++ .../ec2_close_port_1521/requirements-dev.txt | 6 + .../jobs/ec2_close_port_1521/requirements.txt | 6 + .../jobs/ec2_close_port_20/README.md | 53 +++++++ .../jobs/ec2_close_port_20/__init__.py | 0 .../jobs/ec2_close_port_20/constraints.txt | 43 +++++ .../ec2_close_port_20/ec2_close_port_20.py | 147 ++++++++++++++++++ .../ec2_close_port_20/minimum_policy.json | 14 ++ .../ec2_close_port_20/requirements-dev.txt | 6 + .../jobs/ec2_close_port_20/requirements.txt | 6 + .../jobs/ec2_close_port_21/README.md | 54 +++++++ .../jobs/ec2_close_port_21/__init__.py | 0 .../jobs/ec2_close_port_21/constraints.txt | 43 +++++ .../ec2_close_port_21/ec2_close_port_21.py | 144 +++++++++++++++++ .../ec2_close_port_21/minimum_policy.json | 14 ++ .../ec2_close_port_21/requirements-dev.txt | 6 + .../jobs/ec2_close_port_21/requirements.txt | 6 + .../jobs/ec2_close_port_23/README.md | 53 +++++++ .../jobs/ec2_close_port_23/__init__.py | 0 .../jobs/ec2_close_port_23/constraints.txt | 43 +++++ .../ec2_close_port_23/ec2_close_port_23.py | 144 +++++++++++++++++ .../ec2_close_port_23/minimum_policy.json | 14 ++ .../ec2_close_port_23/requirements-dev.txt | 6 + .../jobs/ec2_close_port_23/requirements.txt | 6 + .../jobs/ec2_close_port_27017/README.md | 54 +++++++ .../jobs/ec2_close_port_27017/__init__.py | 0 .../jobs/ec2_close_port_27017/constraints.txt | 43 +++++ .../ec2_close_port_27017.py | 144 +++++++++++++++++ .../ec2_close_port_27017/minimum_policy.json | 14 ++ .../ec2_close_port_27017/requirements-dev.txt | 6 + .../ec2_close_port_27017/requirements.txt | 6 + .../jobs/ec2_close_port_3306/README.md | 54 +++++++ .../jobs/ec2_close_port_3306/__init__.py | 0 .../jobs/ec2_close_port_3306/constraints.txt | 43 +++++ .../ec2_close_port_3306.py | 144 +++++++++++++++++ .../ec2_close_port_3306/minimum_policy.json | 14 ++ .../ec2_close_port_3306/requirements-dev.txt | 6 + .../jobs/ec2_close_port_3306/requirements.txt | 6 + .../jobs/ec2_close_port_5439/README.md | 53 +++++++ .../jobs/ec2_close_port_5439/__init__.py | 0 .../jobs/ec2_close_port_5439/constraints.txt | 43 +++++ .../ec2_close_port_5439.py | 144 +++++++++++++++++ .../ec2_close_port_5439/minimum_policy.json | 14 ++ .../ec2_close_port_5439/requirements-dev.txt | 6 + .../jobs/ec2_close_port_5439/requirements.txt | 6 + .../jobs/ec2_close_port_5601/README.md | 53 +++++++ .../jobs/ec2_close_port_5601/__init__.py | 0 .../jobs/ec2_close_port_5601/constraints.txt | 43 +++++ .../ec2_close_port_5601.py | 144 +++++++++++++++++ .../ec2_close_port_5601/minimum_policy.json | 14 ++ .../ec2_close_port_5601/requirements-dev.txt | 6 + .../jobs/ec2_close_port_5601/requirements.txt | 6 + .../jobs/ec2_close_port_8080/README.md | 53 +++++++ .../jobs/ec2_close_port_8080/__init__.py | 0 .../jobs/ec2_close_port_8080/constraints.txt | 43 +++++ .../ec2_close_port_8080.py | 144 +++++++++++++++++ .../jobs/ec2_close_port_8080/finding.json | 8 + .../ec2_close_port_8080/minimum_policy.json | 14 ++ .../ec2_close_port_8080/requirements-dev.txt | 6 + .../jobs/ec2_close_port_8080/requirements.txt | 6 + .../jobs/ec2_close_port_9200_9300/README.md | 53 +++++++ .../jobs/ec2_close_port_9200_9300/__init__.py | 0 .../ec2_close_port_9200_9300/constraints.txt | 43 +++++ .../ec2_close_port_9200_9300.py | 145 +++++++++++++++++ .../minimum_policy.json | 14 ++ .../requirements-dev.txt | 6 + .../ec2_close_port_9200_9300/requirements.txt | 6 + test/unit/test_ec2_close_port_1433.py | 39 +++++ test/unit/test_ec2_close_port_1521.py | 39 +++++ test/unit/test_ec2_close_port_20.py | 39 +++++ test/unit/test_ec2_close_port_21.py | 39 +++++ test/unit/test_ec2_close_port_23.py | 39 +++++ test/unit/test_ec2_close_port_27017.py | 39 +++++ test/unit/test_ec2_close_port_3306.py | 39 +++++ test/unit/test_ec2_close_port_5439.py | 39 +++++ test/unit/test_ec2_close_port_5601.py | 39 +++++ test/unit/test_ec2_close_port_8080.py | 39 +++++ test/unit/test_ec2_close_port_9200_9300.py | 39 +++++ tox.ini | 70 ++++++++- 91 files changed, 3451 insertions(+), 1 deletion(-) create mode 100644 remediation_worker/jobs/ec2_close_port_1433/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_1433/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_1433/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py create mode 100644 remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_1433/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_1433/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_1521/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_1521/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_1521/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py create mode 100644 remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_1521/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_1521/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_20/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_20/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_20/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py create mode 100644 remediation_worker/jobs/ec2_close_port_20/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_20/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_20/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_21/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_21/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_21/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py create mode 100644 remediation_worker/jobs/ec2_close_port_21/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_21/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_21/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_23/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_23/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_23/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py create mode 100644 remediation_worker/jobs/ec2_close_port_23/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_23/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_23/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_27017/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_27017/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_27017/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py create mode 100644 remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_27017/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_27017/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_3306/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_3306/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_3306/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py create mode 100644 remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_3306/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_3306/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_5439/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_5439/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_5439/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py create mode 100644 remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_5439/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_5439/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_5601/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_5601/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_5601/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py create mode 100644 remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_5601/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_5601/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_8080/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_8080/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_8080/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py create mode 100644 remediation_worker/jobs/ec2_close_port_8080/finding.json create mode 100644 remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_8080/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_8080/requirements.txt create mode 100644 remediation_worker/jobs/ec2_close_port_9200_9300/README.md create mode 100644 remediation_worker/jobs/ec2_close_port_9200_9300/__init__.py create mode 100644 remediation_worker/jobs/ec2_close_port_9200_9300/constraints.txt create mode 100644 remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py create mode 100644 remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json create mode 100644 remediation_worker/jobs/ec2_close_port_9200_9300/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_9200_9300/requirements.txt create mode 100755 test/unit/test_ec2_close_port_1433.py create mode 100755 test/unit/test_ec2_close_port_1521.py create mode 100755 test/unit/test_ec2_close_port_20.py create mode 100755 test/unit/test_ec2_close_port_21.py create mode 100755 test/unit/test_ec2_close_port_23.py create mode 100755 test/unit/test_ec2_close_port_27017.py create mode 100755 test/unit/test_ec2_close_port_3306.py create mode 100755 test/unit/test_ec2_close_port_5439.py create mode 100755 test/unit/test_ec2_close_port_5601.py create mode 100755 test/unit/test_ec2_close_port_8080.py create mode 100755 test/unit/test_ec2_close_port_9200_9300.py diff --git a/README.md b/README.md index 3239b5b..c17afa2 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,17 @@ The table below lists all the supported jobs with their links. | 21. | 688d093c-3b8d-11eb-adc1-0242ac120002 | S3 bucket should allow only HTTPS requests | [aws-s3-bucket-policy-allow-https](remediation_worker/jobs/aws_s3_bucket_policy_allow_https) | | 22. | 09639b9d-98e8-493b-b8a4-916775a7dea9 | SQS queue policy should restricted access to required users | [aws-sqs-queue-publicly-accessible](remediation_worker/jobs/aws_sqs_queue_publicly_accessible) | | 23. | 1ec4a1f2-3e08-11eb-b378-0242ac130002 | Network ACL should restrict administration ports (3389 and 22) from public access | [aws-ec2-administration-ports-ingress-allowed](remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed) | +| 24. | 5c8c263d7a550e1fb6560c39 | EC2 instance should restrict public access to FTP data port (20) | [ec2-close-port-20](remediation_worker/jobs/ec2_close_port_20) | +| 25. | 4823ede0-7bed-4af0-a182-81c2ada80203 | EC2 instance should restrict public access to Kibana (5601) | [ec2-close-port-5601](remediation_worker/jobs/ec2_close_port_5601) | +| 26. | 5c8c26427a550e1fb6560c41 | EC2 instance should restrict public access to MySQL server port (3306) | [ec2-close-port-3306](remediation_worker/jobs/ec2_close_port_3306) | +| 27. | 5c8c26417a550e1fb6560c3e | EC2 instance should restrict public access to Oracle SQL port (1521) | [ec2-close-port-1521](remediation_worker/jobs/ec2_close_port_1521) | +| 28. | 5c8c26417a550e1fb6560c3d | EC2 instance should restrict public access to SQL Server port (1433) | [ec2-close-port-1433](remediation_worker/jobs/ec2_close_port_1433) | +| 29. | 5c8c263e7a550e1fb6560c3b | EC2 instance should restrict public access to Telnet port (23) | [ec2-close-port-23](remediation_worker/jobs/ec2_close_port_23) | +| 30. | 5c8c263d7a550e1fb6560c3a | EC2 instance should restrict public access to FTP port (21) | [ec2-close-port-21](remediation_worker/jobs/ec2_close_port_21) | +| 31. | 04700175-adbe-49e1-bc7a-bc9605597ce2 | EC2 instance should restrict public access to Elasticsearch ports (9200,9300) | [ec2-close-port-9200_9300](remediation_worker/jobs/ec2_close_port_9200_9300) | +| 32. | 5c8c26427a550e1fb6560c40 | EC2 instance should restrict public access to MongoDB port (27017) | [ec2-close-port-27017](remediation_worker/jobs/ec2_close_port_27017) | +| 33. | 5c8c26407a550e1fb6560c3c | EC2 instance should restrict public access to TCP port (8080) | [ec2-close-port-8080](remediation_worker/jobs/ec2_close_port_8080) | +| 34. | 5c8c26447a550e1fb6560c44 | EC2 instance should restrict public access to Redshift port (5439) | [ec2-close-port-5439](remediation_worker/jobs/ec2_close_port_5439) | ## 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/ec2_close_port_1433/README.md b/remediation_worker/jobs/ec2_close_port_1433/README.md new file mode 100644 index 0000000..5cd1cec --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1433/README.md @@ -0,0 +1,53 @@ +# Close Port 1433 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 1433 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c26417a550e1fb6560c3d + +##### Rule Name: +EC2 instance should restrict public access to SQL Server port (1433) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_1433.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/ec2_close_port_1433/__init__.py b/remediation_worker/jobs/ec2_close_port_1433/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_1433/constraints.txt b/remediation_worker/jobs/ec2_close_port_1433/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1433/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/ec2_close_port_1433/ec2_close_port_1433.py b/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py new file mode 100644 index 0000000..10fb11c --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort1433(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 1433 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 1433 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort1433() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_1433/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_1433/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1433/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/ec2_close_port_1433/requirements.txt b/remediation_worker/jobs/ec2_close_port_1433/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1433/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/ec2_close_port_1521/README.md b/remediation_worker/jobs/ec2_close_port_1521/README.md new file mode 100644 index 0000000..b1a8f9a --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1521/README.md @@ -0,0 +1,54 @@ +# Close Port 1521 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 1521 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c26417a550e1fb6560c3e + +##### Rule Name: +EC2 instance should restrict public access to Oracle SQL port (1521) + + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_1521.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/ec2_close_port_1521/__init__.py b/remediation_worker/jobs/ec2_close_port_1521/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_1521/constraints.txt b/remediation_worker/jobs/ec2_close_port_1521/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1521/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/ec2_close_port_1521/ec2_close_port_1521.py b/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py new file mode 100644 index 0000000..541f9ca --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort1521(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 1521 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 1521 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort1521() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_1521/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_1521/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1521/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/ec2_close_port_1521/requirements.txt b/remediation_worker/jobs/ec2_close_port_1521/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_1521/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/ec2_close_port_20/README.md b/remediation_worker/jobs/ec2_close_port_20/README.md new file mode 100644 index 0000000..6fa507c --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_20/README.md @@ -0,0 +1,53 @@ +# Close Port 20 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 20 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c263d7a550e1fb6560c39 + +##### Rule Name: +EC2 instance should restrict public access to FTP data port (20) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_20.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/ec2_close_port_20/__init__.py b/remediation_worker/jobs/ec2_close_port_20/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_20/constraints.txt b/remediation_worker/jobs/ec2_close_port_20/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_20/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/ec2_close_port_20/ec2_close_port_20.py b/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py new file mode 100644 index 0000000..5180205 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py @@ -0,0 +1,147 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort20(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 20 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 20 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort20() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_20/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_20/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_20/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_20/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_20/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_20/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/ec2_close_port_20/requirements.txt b/remediation_worker/jobs/ec2_close_port_20/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_20/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/ec2_close_port_21/README.md b/remediation_worker/jobs/ec2_close_port_21/README.md new file mode 100644 index 0000000..5a9e9f4 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_21/README.md @@ -0,0 +1,54 @@ +# Close Port 21 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 21 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c263d7a550e1fb6560c3a + +##### Rule Name: +EC2 instance should restrict public access to FTP control port (21) + + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_21.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/ec2_close_port_21/__init__.py b/remediation_worker/jobs/ec2_close_port_21/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_21/constraints.txt b/remediation_worker/jobs/ec2_close_port_21/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_21/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/ec2_close_port_21/ec2_close_port_21.py b/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py new file mode 100644 index 0000000..a6fdb13 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort21(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 21 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 21 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort21() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_21/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_21/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_21/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_21/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_21/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_21/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/ec2_close_port_21/requirements.txt b/remediation_worker/jobs/ec2_close_port_21/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_21/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/ec2_close_port_23/README.md b/remediation_worker/jobs/ec2_close_port_23/README.md new file mode 100644 index 0000000..29668a7 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_23/README.md @@ -0,0 +1,53 @@ +# Close Port 23 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 23 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c263e7a550e1fb6560c3b + +##### Rule Name: +EC2 instance should restrict public access to Telnet port (23) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_23.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/ec2_close_port_23/__init__.py b/remediation_worker/jobs/ec2_close_port_23/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_23/constraints.txt b/remediation_worker/jobs/ec2_close_port_23/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_23/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/ec2_close_port_23/ec2_close_port_23.py b/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py new file mode 100644 index 0000000..37c9069 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort23(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 23 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 23 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort23() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_23/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_23/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_23/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_23/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_23/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_23/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/ec2_close_port_23/requirements.txt b/remediation_worker/jobs/ec2_close_port_23/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_23/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/ec2_close_port_27017/README.md b/remediation_worker/jobs/ec2_close_port_27017/README.md new file mode 100644 index 0000000..2439b63 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_27017/README.md @@ -0,0 +1,54 @@ +# Close Port 27017 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 27017 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c26427a550e1fb6560c40 + +##### Rule Name: +EC2 instance should restrict public access to MongoDB server port (27017) + + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_27017.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/ec2_close_port_27017/__init__.py b/remediation_worker/jobs/ec2_close_port_27017/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_27017/constraints.txt b/remediation_worker/jobs/ec2_close_port_27017/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_27017/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/ec2_close_port_27017/ec2_close_port_27017.py b/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py new file mode 100644 index 0000000..24c8744 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort27017(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 27017 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 27017 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort27017() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_27017/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_27017/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_27017/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/ec2_close_port_27017/requirements.txt b/remediation_worker/jobs/ec2_close_port_27017/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_27017/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/ec2_close_port_3306/README.md b/remediation_worker/jobs/ec2_close_port_3306/README.md new file mode 100644 index 0000000..27e409b --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_3306/README.md @@ -0,0 +1,54 @@ +# Close Port 3306 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 3306 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c26427a550e1fb6560c41 + +##### Rule Name: +EC2 instance should restrict public access to MySQL server port (3306) + + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_3306.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/ec2_close_port_3306/__init__.py b/remediation_worker/jobs/ec2_close_port_3306/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_3306/constraints.txt b/remediation_worker/jobs/ec2_close_port_3306/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_3306/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/ec2_close_port_3306/ec2_close_port_3306.py b/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py new file mode 100644 index 0000000..fa81036 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort3306(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 3306 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 3306 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort3306() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_3306/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_3306/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_3306/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/ec2_close_port_3306/requirements.txt b/remediation_worker/jobs/ec2_close_port_3306/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_3306/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/ec2_close_port_5439/README.md b/remediation_worker/jobs/ec2_close_port_5439/README.md new file mode 100644 index 0000000..807aeba --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5439/README.md @@ -0,0 +1,53 @@ +# Close Port 5439 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 5439 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c26447a550e1fb6560c44 + +##### Rule Name: +EC2 instance should restrict public access to Redshift port (5439) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_5439.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/ec2_close_port_5439/__init__.py b/remediation_worker/jobs/ec2_close_port_5439/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_5439/constraints.txt b/remediation_worker/jobs/ec2_close_port_5439/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5439/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/ec2_close_port_5439/ec2_close_port_5439.py b/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py new file mode 100644 index 0000000..716c29c --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort5439(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 5439 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 5439 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort5439() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_5439/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_5439/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5439/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/ec2_close_port_5439/requirements.txt b/remediation_worker/jobs/ec2_close_port_5439/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5439/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/ec2_close_port_5601/README.md b/remediation_worker/jobs/ec2_close_port_5601/README.md new file mode 100644 index 0000000..d0bd89b --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5601/README.md @@ -0,0 +1,53 @@ +# Close Port 5601 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 5601 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +4823ede0-7bed-4af0-a182-81c2ada80203 + +##### Rule Name: +EC2 instance should restrict public access to Kibana port (5601) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_5601.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/ec2_close_port_5601/__init__.py b/remediation_worker/jobs/ec2_close_port_5601/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_5601/constraints.txt b/remediation_worker/jobs/ec2_close_port_5601/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5601/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/ec2_close_port_5601/ec2_close_port_5601.py b/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py new file mode 100644 index 0000000..d8f1389 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort5601(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 5601 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 5601 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort5601() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_5601/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_5601/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5601/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/ec2_close_port_5601/requirements.txt b/remediation_worker/jobs/ec2_close_port_5601/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_5601/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/ec2_close_port_8080/README.md b/remediation_worker/jobs/ec2_close_port_8080/README.md new file mode 100644 index 0000000..280bdf9 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_8080/README.md @@ -0,0 +1,53 @@ +# Close Port 8080 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 22 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +5c8c26407a550e1fb6560c3c + +##### Rule Name: +EC2 instance should restrict public access to TCP port (8080) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_8080.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/ec2_close_port_8080/__init__.py b/remediation_worker/jobs/ec2_close_port_8080/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_8080/constraints.txt b/remediation_worker/jobs/ec2_close_port_8080/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_8080/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/ec2_close_port_8080/ec2_close_port_8080.py b/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py new file mode 100644 index 0000000..69f16c4 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py @@ -0,0 +1,144 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort8080(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 8080 of all security groups attached to an EC2 instance. + + :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 + """ + + port = 8080 + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info("successfully executed remediation") + + 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 = EC2ClosePort8080() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_8080/finding.json b/remediation_worker/jobs/ec2_close_port_8080/finding.json new file mode 100644 index 0000000..1e9d64a --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_8080/finding.json @@ -0,0 +1,8 @@ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-0b0c39c38f5a42f95", + "Region": "us-west-1" + } + } +} diff --git a/remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_8080/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_8080/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_8080/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/ec2_close_port_8080/requirements.txt b/remediation_worker/jobs/ec2_close_port_8080/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_8080/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/ec2_close_port_9200_9300/README.md b/remediation_worker/jobs/ec2_close_port_9200_9300/README.md new file mode 100644 index 0000000..2533c24 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/README.md @@ -0,0 +1,53 @@ +# Close Port 9200, 9300 for all Security Groups associated with an EC2 Instance + +This job blocks public access to port 9200, 9300 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. + +### Applicable Rule + +##### Rule ID: +04700175-adbe-49e1-bc7a-bc9605597ce2 + +##### Rule Name: +EC2 instance should restrict public access to Elasticsearch ports (9200 and 9300) + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. + +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 ec2_close_port_9200_9300.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/ec2_close_port_9200_9300/__init__.py b/remediation_worker/jobs/ec2_close_port_9200_9300/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/constraints.txt b/remediation_worker/jobs/ec2_close_port_9200_9300/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/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/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py b/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py new file mode 100644 index 0000000..1369ae2 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py @@ -0,0 +1,145 @@ +# 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 +from botocore.exceptions import ClientError +import json +import logging +import sys + +import boto3 + +logging.basicConfig(level=logging.INFO) + + +class EC2ClosePort9200_9300(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 9200_9300 of all security groups attached to an EC2 instance. + + :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 + """ + + ports = [9200, 9300] + + for port in ports: + 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"] + + # Revoke ipv4 permission + logging.info("revoking ivp4 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(' CidrIp="0.0.0.0/0"') + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + CidrIp="0.0.0.0/0", + FromPort=port, + GroupId=security_group_id, + IpProtocol="tcp", + ToPort=port, + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + # Revoke ipv6 permission + logging.info("revoking ivp6 permissions") + try: + logging.info(" executing client.revoke_security_group_ingress") + logging.info(f" FromPort={port}") + logging.info(f" GroupId={security_group_id}") + logging.info(' IpProtocol="tcp"') + logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') + logging.info(f" ToPort={port}") + client.revoke_security_group_ingress( + GroupId=security_group_id, + IpPermissions=[ + { + "FromPort": port, + "IpProtocol": "tcp", + "Ipv6Ranges": [{"CidrIpv6": "::/0"}], + "ToPort": port, + }, + ], + ) + except ClientError as e: + if "InvalidPermission.NotFound" not in str(e): + logging.error(f"{str(e)}") + raise + + logging.info(f"successfully executed remediation for port: {port}") + + 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 = EC2ClosePort9200_9300() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json new file mode 100644 index 0000000..3140049 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "EC2ClosePort22", + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "ec2:RevokeSecurityGroupIngress" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_9200_9300/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/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/ec2_close_port_9200_9300/requirements.txt b/remediation_worker/jobs/ec2_close_port_9200_9300/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/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_ec2_close_port_1433.py b/test/unit/test_ec2_close_port_1433.py new file mode 100755 index 0000000..f9143e7 --- /dev/null +++ b/test/unit/test_ec2_close_port_1433.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.ec2_close_port_1433.ec2_close_port_1433 import EC2ClosePort1433 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort1433: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort1433() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_1521.py b/test/unit/test_ec2_close_port_1521.py new file mode 100755 index 0000000..df85458 --- /dev/null +++ b/test/unit/test_ec2_close_port_1521.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.ec2_close_port_1521.ec2_close_port_1521 import EC2ClosePort1521 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort1521: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort1521() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_20.py b/test/unit/test_ec2_close_port_20.py new file mode 100755 index 0000000..9d30462 --- /dev/null +++ b/test/unit/test_ec2_close_port_20.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.ec2_close_port_20.ec2_close_port_20 import EC2ClosePort20 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort20: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort20() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_21.py b/test/unit/test_ec2_close_port_21.py new file mode 100755 index 0000000..b83fb89 --- /dev/null +++ b/test/unit/test_ec2_close_port_21.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.ec2_close_port_21.ec2_close_port_21 import EC2ClosePort21 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort21: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort21() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_23.py b/test/unit/test_ec2_close_port_23.py new file mode 100755 index 0000000..3b86ffd --- /dev/null +++ b/test/unit/test_ec2_close_port_23.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.ec2_close_port_23.ec2_close_port_23 import EC2ClosePort23 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort23: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort23() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_27017.py b/test/unit/test_ec2_close_port_27017.py new file mode 100755 index 0000000..7fae40d --- /dev/null +++ b/test/unit/test_ec2_close_port_27017.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.ec2_close_port_27017.ec2_close_port_27017 import EC2ClosePort27017 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort27017: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort27017() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_3306.py b/test/unit/test_ec2_close_port_3306.py new file mode 100755 index 0000000..680ca02 --- /dev/null +++ b/test/unit/test_ec2_close_port_3306.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.ec2_close_port_3306.ec2_close_port_3306 import EC2ClosePort3306 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort3306: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort3306() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_5439.py b/test/unit/test_ec2_close_port_5439.py new file mode 100755 index 0000000..b531da3 --- /dev/null +++ b/test/unit/test_ec2_close_port_5439.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.ec2_close_port_5439.ec2_close_port_5439 import EC2ClosePort5439 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort5439: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort5439() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_5601.py b/test/unit/test_ec2_close_port_5601.py new file mode 100755 index 0000000..9567cd7 --- /dev/null +++ b/test/unit/test_ec2_close_port_5601.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.ec2_close_port_5601.ec2_close_port_5601 import EC2ClosePort5601 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort5601: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort5601() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_8080.py b/test/unit/test_ec2_close_port_8080.py new file mode 100755 index 0000000..1b75f9e --- /dev/null +++ b/test/unit/test_ec2_close_port_8080.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.ec2_close_port_8080.ec2_close_port_8080 import EC2ClosePort8080 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort8080: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort8080() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/test/unit/test_ec2_close_port_9200_9300.py b/test/unit/test_ec2_close_port_9200_9300.py new file mode 100755 index 0000000..49b61b1 --- /dev/null +++ b/test/unit/test_ec2_close_port_9200_9300.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.ec2_close_port_9200_9300.ec2_close_port_9200_9300 import EC2ClosePort9200_9300 + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } +} +""" + + +class TestEC2ClosePort9200_9300: + def test_parse_payload(self, valid_payload): + obj = EC2ClosePort9200_9300() + param, region = obj.parse(valid_payload) + assert "instance_id" in param + assert param["instance_id"] == "i-00347a2be30cf1a15" diff --git a/tox.ini b/tox.ini index 01fe23f..8124eba 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,17 @@ minversion = 3.6.0 skip_missing_interpreters = true envlist = + unit-ec2-close-port-5601 + unit-ec2-close-port-5439 + unit-ec2-close-port-3306 + unit-ec2-close-port-27017 + unit-ec2-close-port-23 + unit-ec2-close-port-21 + unit-ec2-close-port-20 + unit-ec2-close-port-1521 + unit-ec2-close-port-1433 + unit-ec2-close-port-8080 + unit-ec2-close-port-8200-9300 unit-security-group-close-port-5432 unit-s3-remove-public-admin-acl unit-s3-enable-access-logging @@ -42,6 +53,7 @@ envlist = unit-aws-s3-bucket-policy-allow-https unit-aws-sqs-queue-publicly-accessible + [testenv] passenv = # Prevent Python bytecode files from being created @@ -292,4 +304,60 @@ deps = -r remediation_worker/jobs/aws_s3_bucket_policy_allow_https/requirements- description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_sqs_queue_publicly_accessible.py -deps = -r remediation_worker/jobs/aws_sqs_queue_publicly_accessible/requirements-dev.txt \ No newline at end of file +deps = -r remediation_worker/jobs/aws_sqs_queue_publicly_accessible/requirements-dev.txt + +[testenv:unit-ec2-close-port-5601] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_5601.py +deps = -r remediation_worker/jobs/ec2_close_port_3389/requirements-dev.txt + +[testenv:unit-ec2-close-port-5439] +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_5439.py +changedir = test +deps = -r remediation_worker/jobs/ec2_close_port_5439/requirements-dev.txt + +[testenv:unit-ec2-close-port-3306] +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_3306.py +deps = -r remediation_worker/jobs/ec2_close_port_3306/requirements-dev.txt + +[testenv:unit-ec2-close-port-27017] +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_27017.py +deps = -r remediation_worker/jobs/ec2_close_port_27017/requirements-dev.txt + +[testenv:unit-ec2-close-port-23] +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_23.py +deps = -r remediation_worker/jobs/ec2_close_port_23/requirements-dev.txt + +[testenv:unit-ec2-close-port-21] +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_21.py +deps = -r remediation_worker/jobs/ec2_close_port_21/requirements-dev.txt + +[testenv:unit-ec2-close-port-20] +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_20.py +deps = -r remediation_worker/jobs/ec2_close_port_20/requirements-dev.txt + +[testenv:unit-ec2-close-port-1521] +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_1521.py +deps = -r remediation_worker/jobs/ec2_close_port_1521/requirements-dev.txt + +[testenv:unit-ec2-close-port-1433] +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_1433.py +deps = -r remediation_worker/jobs/ec2_close_port_1433/requirements-dev.txt + +[testenv:unit-ec2-close-port-9200-9300] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_9200_9300.py +deps = -r remediation_worker/jobs/ec2_close_port_9200_9300/requirements-dev.txt + +[testenv:unit-ec2-close-port-8080] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_8080.py +deps = -r remediation_worker/jobs/ec2_close_port_8080/requirements-dev.txt From a0a2d04efccf66529766f6aa47a731d83c8c16d4 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Tue, 29 Jun 2021 21:37:42 +0530 Subject: [PATCH 02/23] PLA-26195 - Handled PrincipalNotFound Exception in sql auditing job (#98) --- .../azure_sql_auditing_on_server.py | 60 ++++++++++++------- .../test_aws_s3_cloudtrail_public_access.py | 10 ++-- ..._key_vault_expiry_date_set_for_all_keys.py | 2 +- ...y_vault_expiry_date_set_for_all_secrets.py | 2 +- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index df05e04..2de4793 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -12,6 +12,7 @@ from azure.mgmt.monitor import MonitorClient from azure.identity import ClientSecretCredential from azure.graphrbac import GraphRbacManagementClient +from azure.core.exceptions import HttpResponseError from azure.mgmt.keyvault import KeyVaultManagementClient from azure.common.credentials import ServicePrincipalCredentials from azure.mgmt.authorization import AuthorizationManagementClient @@ -70,7 +71,6 @@ MAX_COUNT_REGION = 6 MAX_COUNT_COMPONENT = 4 - def generate_name(region, subscription_id, resource_group_name): """Generates a name for the resource :param region: location in which the resource exists @@ -741,16 +741,28 @@ def remediate( ) scope = f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Storage/storageAccounts/{stg_account.name}" - # Assign Storage Blob Contributer role to the SQL Server - self.create_role_assignment( - stg_name, - subscription_id, - client_authorization, - guid, - scope, - principalId, - sql_server_name, + role_definition_id = f"/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe" + status = self.check_role_assignment( + client_authorization, scope, principalId, role_definition_id ) + try: + if status is False: + # Assign Storage Blob Contributer role to the SQL Server + self.create_role_assignment( + stg_name, + subscription_id, + client_authorization, + guid, + scope, + principalId, + sql_server_name, + ) + except HttpResponseError as e: + if e.status_code == 400: + logging.error(f"{str(e)}") + logging.info("In case of PrincipalNotFound error, retry remediating this finding after few minutes") + return -1 + else: # If the Storage Account Created by CHSS exists stg_id = stg_account.id @@ -763,17 +775,23 @@ def remediate( status = self.check_role_assignment( client_authorization, scope, principalId, role_definition_id ) - if status is False: - # If role assignment does not exists, assign the Storage Blob Contributer Role to the SQL Server - self.create_role_assignment( - stg_account.name, - subscription_id, - client_authorization, - guid, - scope, - principalId, - sql_server_name, - ) + try: + if status is False: + # If role assignment does not exists, assign the Storage Blob Contributer Role to the SQL Server + self.create_role_assignment( + stg_account.name, + subscription_id, + client_authorization, + guid, + scope, + principalId, + sql_server_name, + ) + except HttpResponseError as e: + if e.status_code == 400: + logging.error(f"{str(e)}") + logging.info("In case of PrincipalNotFound error, retry remediating this finding after few minutes") + return -1 else: logging.error("Resource group name not found") return -1 diff --git a/test/unit/test_aws_s3_cloudtrail_public_access.py b/test/unit/test_aws_s3_cloudtrail_public_access.py index c2b81df..3682a27 100644 --- a/test/unit/test_aws_s3_cloudtrail_public_access.py +++ b/test/unit/test_aws_s3_cloudtrail_public_access.py @@ -17,7 +17,7 @@ from botocore.exceptions import ClientError from remediation_worker.jobs.aws_s3_cloudtrail_public_access.aws_s3_cloudtrail_public_access import ( - CloudtrailS3RemovePublicAcces, + CloudtrailS3RemovePublicAccess, ) @@ -41,7 +41,7 @@ def valid_payload(): class TestCloudtrailS3PublicAccess(object): def test_parse_payload(self, valid_payload): - params = CloudtrailS3RemovePublicAcces().parse(valid_payload) + params = CloudtrailS3RemovePublicAccess().parse(valid_payload) assert params["region"] == "region" assert params["cloudtrail_name"] == "CloudTrail_name" assert params["cloud_account_id"] == "cloud_account_id" @@ -49,7 +49,7 @@ def test_parse_payload(self, valid_payload): def test_remediate_success_with_bucket_policy_public(self): client = Mock() cloudtrail_client = Mock() - action = CloudtrailS3RemovePublicAcces() + action = CloudtrailS3RemovePublicAccess() trail = { "Trail": { "Name": "CloudTrail_name", @@ -126,7 +126,7 @@ def test_remediate_success_with_bucket_policy_public(self): def test_remediate_success_without_bucket_policy_public(self): client = Mock() cloudtrail_client = Mock() - action = CloudtrailS3RemovePublicAcces() + action = CloudtrailS3RemovePublicAccess() trail = { "Trail": { "Name": "CloudTrail_name", @@ -188,6 +188,6 @@ def put_public_access_block(self, **kwargs): ) client = TestClient() - action = CloudtrailS3RemovePublicAcces() + action = CloudtrailS3RemovePublicAccess() with pytest.raises(Exception): assert action.remediate(client, "bucket_name", "cloud_account_id") diff --git a/test/unit/test_azure_key_vault_expiry_date_set_for_all_keys.py b/test/unit/test_azure_key_vault_expiry_date_set_for_all_keys.py index 0ea6c9e..938b7ec 100644 --- a/test/unit/test_azure_key_vault_expiry_date_set_for_all_keys.py +++ b/test/unit/test_azure_key_vault_expiry_date_set_for_all_keys.py @@ -29,7 +29,7 @@ def valid_payload(): "FindingInfo": { "FindingId": "d3bb1d9a-fe52-4458-9935-47183f140e6b", "ObjectId": "key_vault_name.key_name", - "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.KeyVault.Key.d687b1a3-9b78-43b1-a17b-7de297fd1fce.integration-tests-postgresql-b3wx.Key.postgresqlwnuuobrlrwngs\\",\\"entityName\\":\\"key_vault_name.key_name\\",\\"entityType\\":\\"Azure.KeyVault.Key\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"PostgreSQL\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.KeyVault.Key.d687b1a3-9b78-43b1-a17b-7de297fd1fce.integration-tests-postgresql-b3wx.Key.postgresqlwnuuobrlrwngs\\",\\"entityName\\":\\"key_vault_name.key_name\\",\\"entityType\\":\\"Azure.KeyVault.Key\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"PostgreSQL\\", \\"properties\\":[{\\"name\\":\\"System:SourceEntityId\\",\\"stringV\\":\\"Azure.KeyVault.8aa70cc7-bf51-4e8c-baa0-368cb78b3c0c.resource_group_name.Vault.rem-testsjehb\\",\\"type\\":\\"string\\"}]}", "Region": "region" } } diff --git a/test/unit/test_azure_key_vault_expiry_date_set_for_all_secrets.py b/test/unit/test_azure_key_vault_expiry_date_set_for_all_secrets.py index c635dd4..5c88cf7 100644 --- a/test/unit/test_azure_key_vault_expiry_date_set_for_all_secrets.py +++ b/test/unit/test_azure_key_vault_expiry_date_set_for_all_secrets.py @@ -29,7 +29,7 @@ def valid_payload(): "FindingInfo": { "FindingId": "d3bb1d9a-fe52-4458-9935-47183f140e6b", "ObjectId": "key_vault_name.secret_name", - "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.KeyVault.Secret.d687b1a3-9b78-43b1-a17b-7de297fd1fce.integration-tests-postgresql-b3wx.Key.postgresqlwnuuobrlrwngs\\",\\"entityName\\":\\"key_vault_name.key_name\\",\\"entityType\\":\\"Azure.KeyVault.Secret\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"PostgreSQL\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.KeyVault.Secret.d687b1a3-9b78-43b1-a17b-7de297fd1fce.integration-tests-postgresql-b3wx.Key.postgresqlwnuuobrlrwngs\\",\\"entityName\\":\\"key_vault_name.key_name\\",\\"entityType\\":\\"Azure.KeyVault.Secret\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"PostgreSQL\\", \\"properties\\":[{\\"name\\":\\"System:SourceEntityId\\",\\"stringV\\":\\"Azure.KeyVault.8aa70cc7-bf51-4e8c-baa0-368cb78b3c0c.resource_group_name.Vault.rem-testsjehb\\",\\"type\\":\\"string\\"}]}", "Region": "region" } } From b82785d11f03fa6bd00a9d34092d0bf22e7839b9 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Fri, 30 Jul 2021 02:55:52 +0530 Subject: [PATCH 03/23] PLA-24844 - Remediation job to restrict default security group access (#85) * PLA-24844 - Remediation job to restrict default security group access * PLA-24844 - Remediation job to restrict default security group access * Updated the remediation job code --- .../README.md | 68 ++++++++++ .../__init__.py | 0 .../aws_ec2_default_security_group_traffic.py | 123 ++++++++++++++++++ .../constraints.txt | 43 ++++++ .../minimum_policy.json | 15 +++ .../requirements-dev.txt | 9 ++ .../requirements.txt | 6 + ..._aws_ec2_default_security_group_traffic.py | 62 +++++++++ 8 files changed, 326 insertions(+) create mode 100644 remediation_worker/jobs/aws_ec2_default_security_group_traffic/README.md create mode 100644 remediation_worker/jobs/aws_ec2_default_security_group_traffic/__init__.py create mode 100644 remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py create mode 100644 remediation_worker/jobs/aws_ec2_default_security_group_traffic/constraints.txt create mode 100644 remediation_worker/jobs/aws_ec2_default_security_group_traffic/minimum_policy.json create mode 100644 remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements-dev.txt create mode 100644 remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt create mode 100644 test/unit/test_aws_ec2_default_security_group_traffic.py diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/README.md b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/README.md new file mode 100644 index 0000000..8c2870e --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/README.md @@ -0,0 +1,68 @@ +# Configure default Security Group to restrict all access + +This job removes all the Ingress and Egress Rules of a default Security Group to restrict all access. + +### Applicable Rule + +##### Rule ID: +5c8c25f37a550e1fb6560bca + +##### Rule Name: +EC2 VPC default security group should restrict all access + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `ec2:DescribeSecurityGroupRules`, `ec2:RevokeSecurityGroupIngress` and `ec2:RevokeSecurityGroupEgress`. + +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_default_security_group_traffic.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_ec2_default_security_group_traffic/__init__.py b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py new file mode 100644 index 0000000..c40a97d --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py @@ -0,0 +1,123 @@ +# 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 DefaultSecurityGroupRemoveRules(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) + security_group_id = finding_info.get("ObjectId", None) + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + cloud_account_id = object_chain_dict["cloudAccountId"] + region = finding_info.get("Region") + + logging.info(f"security_group_id: {security_group_id}") + logging.info(f"region: {region}") + logging.info(f"cloud_account_id: {cloud_account_id}") + + if security_group_id is None: + raise Exception( + "Missing parameters for 'payload.notificationInfo.ObjectId'." + ) + + return { + "security_group_id": security_group_id, + "region": region, + "cloud_account_id": cloud_account_id, + } + + def remediate(self, client, security_group_id, region, cloud_account_id): + """Restrict all access for EC2 VPC default security group. + :param client: Instance of the AWS boto3 client. + :param security_group_id: The ID of the security group. + :param region: Region in which the security group exists. + :param cloud_account_id: AWS Account no. + :type security_group_id: str. + :type region: str. + :type cloud_account_id: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: botocore.exceptions.ClientError + """ + try: + logging.info(" executing client.describe_security_group_rules") + logging.info(f" group-id: {security_group_id}") + 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["IsEgress"]: + logging.info(" executing client.revoke_security_group_egress") + logging.info(f" GroupId: {security_group_id}") + logging.info(f" SecurityGroupRuleIds: {rule['SecurityGroupRuleId']}") + client.revoke_security_group_egress( + GroupId=security_group_id, + SecurityGroupRuleIds=[rule["SecurityGroupRuleId"]] + ) + else: + 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 = self.parse(args[1]) + client = boto3.client("ec2", params["region"]) + logging.info("acquired ec2 client and parsed params - starting remediation") + rc = self.remediate(client=client, **params) + return rc + + +if __name__ == "__main__": + logging.info("aws_ec2_default_security_group_traffic.py called - running now") + obj = DefaultSecurityGroupRemoveRules() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/constraints.txt b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/constraints.txt new file mode 100644 index 0000000..c5f48f9 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/constraints.txt @@ -0,0 +1,43 @@ +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +s3transfer==0.3.4 \ + --hash=sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed \ + --hash=sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +urllib3==1.26.3 \ + --hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \ + --hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73 \ No newline at end of file diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/minimum_policy.json b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/minimum_policy.json new file mode 100644 index 0000000..ca52fb0 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/minimum_policy.json @@ -0,0 +1,15 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "RemoveDefaultSecurityGroupRules", + "Effect": "Allow", + "Action": [ + "ec2:DescribeSecurityGroupRules", + "ec2:RevokeSecurityGroupIngress", + "ec2:RevokeSecurityGroupEgress" + ], + "Resource": "*" + } + ] +} \ No newline at end of file diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements-dev.txt b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements-dev.txt new file mode 100644 index 0000000..0babf10 --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements-dev.txt @@ -0,0 +1,9 @@ +-r requirements.txt +-c constraints.txt + +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad \ No newline at end of file diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt new file mode 100644 index 0000000..474dc8e --- /dev/null +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt @@ -0,0 +1,6 @@ +boto3==1.18.4 \ + --hash=sha256:649ed1ca205f5ee0b0328d54580780aebc1a7a05681a24f6ee05253007ca48d8 \ + --hash=sha256:7079b40bd6621c54a0385a8fc11240cff4318a4d487292653e393e18254f5d94 +botocore==1.19.60 \ + --hash=sha256:423a1a9502bd7bc5db8c6e64f9374f64d8ac18e6b870278a9ff65f59d268cd58 \ + --hash=sha256:80dd615a34c7e2c73606070a9358f7b5c1cb0c9989348306c1c9ddff45bb6ebe \ No newline at end of file diff --git a/test/unit/test_aws_ec2_default_security_group_traffic.py b/test/unit/test_aws_ec2_default_security_group_traffic.py new file mode 100644 index 0000000..ce451bf --- /dev/null +++ b/test/unit/test_aws_ec2_default_security_group_traffic.py @@ -0,0 +1,62 @@ +# 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 mock import Mock + +from remediation_worker.jobs.aws_ec2_default_security_group_traffic.aws_ec2_default_security_group_traffic import ( + DefaultSecurityGroupRemoveRules, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5e103dcc90f363146cd", + "Service": "EC2", + "FindingInfo": { + "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "ObjectId": "security_group_id", + "ObjectChain": "{\\"cloudAccountId\\":\\"cloud_account_id\\",\\"entityId\\":\\"AWS.EC2.159636093902.us-west-2.Trail.test-remediation\\",\\"entityName\\":\\"remediation-cloudtrail\\",\\"entityType\\":\\"AWS.CloudTrail.Trail\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"153894897389\\",\\"provider\\":\\"AWS\\",\\"region\\":\\"us-west-2\\",\\"service\\":\\"EC2\\", \\"properties\\":[{\\"name\\":\\"S3BucketName\\",\\"stringV\\":\\"remediation-cloudtrail\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestCloudtrailS3PublicAccess(object): + def test_parse_payload(self, valid_payload): + params = DefaultSecurityGroupRemoveRules().parse(valid_payload) + assert params["security_group_id"] == "security_group_id" + assert params["cloud_account_id"] == "cloud_account_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = DefaultSecurityGroupRemoveRules() + + assert ( + action.remediate(client, "security_group_id", "region", "cloud_account_id") + == 0 + ) + assert client.describe_security_groups.call_count == 1 + + def test_remediate_with_exception(self): + client = Mock() + action = DefaultSecurityGroupRemoveRules() + with pytest.raises(Exception): + assert action.remediate(client, "cloud_account_id") \ No newline at end of file From 5df0afa2d07e0a89c3400d5d6e10963dfacca090 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Fri, 30 Jul 2021 02:59:19 +0530 Subject: [PATCH 04/23] PLA-25429 - Remediation job to set password reuse prevention policy (#89) * PLA-25429 - Remediation job to set password reuse prevention policy * PLA-25429 - Updated unit test * Updated the remediation job code --- .../README.md | 68 ++++++++++++++ .../__init__.py | 0 .../aws_iam_password_reuse_prevention.py | 93 +++++++++++++++++++ .../constraints.txt | 43 +++++++++ .../minimum_policy.json | 13 +++ .../requirements-dev.txt | 9 ++ .../requirements.txt | 6 ++ .../test_aws_iam_password_reuse_prevention.py | 59 ++++++++++++ 8 files changed, 291 insertions(+) create mode 100644 remediation_worker/jobs/aws_iam_password_reuse_prevention/README.md create mode 100644 remediation_worker/jobs/aws_iam_password_reuse_prevention/__init__.py create mode 100644 remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py create mode 100644 remediation_worker/jobs/aws_iam_password_reuse_prevention/constraints.txt create mode 100644 remediation_worker/jobs/aws_iam_password_reuse_prevention/minimum_policy.json create mode 100644 remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements-dev.txt create mode 100644 remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements.txt create mode 100644 test/unit/test_aws_iam_password_reuse_prevention.py diff --git a/remediation_worker/jobs/aws_iam_password_reuse_prevention/README.md b/remediation_worker/jobs/aws_iam_password_reuse_prevention/README.md new file mode 100644 index 0000000..71a2b88 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_reuse_prevention/README.md @@ -0,0 +1,68 @@ +# Set Password Reuse Prevention Policy for an AWS Account + +This job sets password reuse prevention policy for an AWS Account. The number of passwords to remember per user will be set to 24. + +### Applicable Rule + +##### Rule ID: +5c8c26107a550e1fb6560bfc + +##### Rule Name: +IAM password policy should prevent password reuse + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `iam:UpdateAccountPasswordPolicy`. + +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_iam_password_reuse_prevention.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_iam_password_reuse_prevention/__init__.py b/remediation_worker/jobs/aws_iam_password_reuse_prevention/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py b/remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py new file mode 100644 index 0000000..c7a0f3c --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py @@ -0,0 +1,93 @@ +# 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 SetPasswordReusePrevention(object): + 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) + account_id = finding_info.get("ObjectId", None) + + if account_id is None: + raise Exception( + "Missing parameters for 'payload.notificationInfo.ObjectId'." + ) + + logging.info("parsed params") + logging.info(f" account_id: {account_id}") + + return_dict = { + "account_id": account_id, + } + logging.info(return_dict) + return return_dict + + def remediate(self, client, account_id): + """Set Password Reuse Prevention Policy for an AWS account + :param client: Instance of the AWS boto3 client. + :param account_id: AWS Account Id. + :type client: object. + :type account_id: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: botocore.exceptions.ClientError + """ + try: + logging.info( + f"Setting Password Reuse Prevention Policy for the account: {account_id}" + ) + logging.info("executing client.update_account_password_policy") + client.update_account_password_policy(PasswordReusePrevention=24) + logging.info("successfully completed remediation job") + 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 = self.parse(args[1]) + client = boto3.client("iam") + logging.info("acquired iam client and parsed params - starting remediation") + rc = self.remediate(client=client, **params) + return rc + + +if __name__ == "__main__": + logging.info("aws_iam_password_reuse_prevention.py called - running now") + obj = SetPasswordReusePrevention() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/aws_iam_password_reuse_prevention/constraints.txt b/remediation_worker/jobs/aws_iam_password_reuse_prevention/constraints.txt new file mode 100644 index 0000000..68a9723 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_reuse_prevention/constraints.txt @@ -0,0 +1,43 @@ +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +s3transfer==0.3.4 \ + --hash=sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed \ + --hash=sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +urllib3==1.26.3 \ + --hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \ + --hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73 diff --git a/remediation_worker/jobs/aws_iam_password_reuse_prevention/minimum_policy.json b/remediation_worker/jobs/aws_iam_password_reuse_prevention/minimum_policy.json new file mode 100644 index 0000000..d95ddce --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_reuse_prevention/minimum_policy.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "SetPasswordReusePolicy", + "Effect": "Allow", + "Action": [ + "iam:UpdateAccountPasswordPolicy" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements-dev.txt b/remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements-dev.txt new file mode 100644 index 0000000..cf03a93 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements-dev.txt @@ -0,0 +1,9 @@ +-r requirements.txt +-c constraints.txt + +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements.txt b/remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements.txt new file mode 100644 index 0000000..ae343fd --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements.txt @@ -0,0 +1,6 @@ +boto3==1.16.60 \ + --hash=sha256:10e8d9b18a8ae15677e850c7240140b9539635a03098f01dfdd75b2042d15862 \ + --hash=sha256:aee742f2a2315244fb31a507f65d8809fcd0029508c0b12be8611ddd2075b666 +botocore==1.19.60 \ + --hash=sha256:423a1a9502bd7bc5db8c6e64f9374f64d8ac18e6b870278a9ff65f59d268cd58 \ + --hash=sha256:80dd615a34c7e2c73606070a9358f7b5c1cb0c9989348306c1c9ddff45bb6ebe diff --git a/test/unit/test_aws_iam_password_reuse_prevention.py b/test/unit/test_aws_iam_password_reuse_prevention.py new file mode 100644 index 0000000..2d8484e --- /dev/null +++ b/test/unit/test_aws_iam_password_reuse_prevention.py @@ -0,0 +1,59 @@ +# 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 mock import Mock + +from remediation_worker.jobs.aws_iam_password_reuse_prevention.aws_iam_password_reuse_prevention import ( + SetPasswordReusePrevention, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5e103dcc90f363146cd", + "Service": "IAM", + "FindingInfo": { + "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "ObjectId": "account_id", + "ObjectChain": "{\\"cloudAccountId\\":\\"cloud_account_id\\",\\"entityId\\":\\"AWS.IAM.159026902.us-west-2.Key.key_id\\",\\"entityName\\":\\"key_id\\",\\"entityType\\":\\"AWS.IAM.AccountPasswordPolicy\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"156898827089\\",\\"provider\\":\\"AWS\\",\\"region\\":\\"us-west-2\\",\\"service\\":\\"CloudTrail\\", \\"properties\\":[{\\"name\\":\\"KeyState\\",\\"stringV\\":\\"Enabled\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestSetPasswordReusePolicy(object): + def test_parse_payload(self, valid_payload): + params = SetPasswordReusePrevention().parse(valid_payload) + assert params["account_id"] == "account_id" + + def test_remediate_success(self): + client = Mock() + action = SetPasswordReusePrevention() + assert action.remediate(client, "account_id") == 0 + assert client.update_account_password_policy.call_count == 1 + call_args = client.update_account_password_policy.call_args + password_reuse_policy = call_args[1]["PasswordReusePrevention"] + assert password_reuse_policy == 24 + + def test_remediate_with_exception(self): + client = Mock() + action = SetPasswordReusePrevention() + with pytest.raises(Exception): + assert action.remediate(client, "account_id") From 4db26ffa548da9c4c6fd5fccd83bc879411f9749 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Fri, 30 Jul 2021 03:00:08 +0530 Subject: [PATCH 05/23] PLA-25428 - Remediation Job to set minimum password length (#90) --- .../README.md | 68 ++++++++++++++ .../__init__.py | 0 .../aws_iam_password_policy_min_length.py | 93 +++++++++++++++++++ .../constraints.txt | 43 +++++++++ .../minimum_policy.json | 13 +++ .../requirements-dev.txt | 9 ++ .../requirements.txt | 6 ++ ...test_aws_iam_password_policy_min_length.py | 59 ++++++++++++ 8 files changed, 291 insertions(+) create mode 100644 remediation_worker/jobs/aws_iam_password_policy_min_length/README.md create mode 100644 remediation_worker/jobs/aws_iam_password_policy_min_length/__init__.py create mode 100644 remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py create mode 100644 remediation_worker/jobs/aws_iam_password_policy_min_length/constraints.txt create mode 100644 remediation_worker/jobs/aws_iam_password_policy_min_length/minimum_policy.json create mode 100644 remediation_worker/jobs/aws_iam_password_policy_min_length/requirements-dev.txt create mode 100644 remediation_worker/jobs/aws_iam_password_policy_min_length/requirements.txt create mode 100644 test/unit/test_aws_iam_password_policy_min_length.py diff --git a/remediation_worker/jobs/aws_iam_password_policy_min_length/README.md b/remediation_worker/jobs/aws_iam_password_policy_min_length/README.md new file mode 100644 index 0000000..c487cac --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_policy_min_length/README.md @@ -0,0 +1,68 @@ +# Set minimum password length for an AWS account. + +This job sets a minimum password length to 14 for an AWS Account. + +### Applicable Rule + +##### Rule ID: +5c8c260b7a550e1fb6560bf4 + +##### Rule Name: +IAM password policy should set a minimum length + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `iam:UpdateAccountPasswordPolicy`. + +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_iam_password_policy_min_length.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_iam_password_policy_min_length/__init__.py b/remediation_worker/jobs/aws_iam_password_policy_min_length/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py b/remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py new file mode 100644 index 0000000..0c07e4b --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py @@ -0,0 +1,93 @@ +# 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 SetPasswordMinimumLength(object): + 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) + account_id = finding_info.get("ObjectId", None) + + if account_id is None: + raise Exception( + "Missing parameters for 'payload.notificationInfo.ObjectId'." + ) + + logging.info("parsed params") + logging.info(f" account_id: {account_id}") + + return_dict = { + "account_id": account_id, + } + logging.info(return_dict) + return return_dict + + def remediate(self, client, account_id): + """Set Minimum Password length for an account + :param client: Instance of the AWS boto3 client. + :param account_id: AWS Account Id. + :type client: object. + :type account_id: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: botocore.exceptions.ClientError + """ + try: + logging.info( + f"Setting Minimum Password length for the account: {account_id}" + ) + logging.info("executing client.update_account_password_policy") + client.update_account_password_policy(MinimumPasswordLength=14) + logging.info("successfully completed remediation job") + 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 = self.parse(args[1]) + client = boto3.client("iam") + logging.info("acquired iam client and parsed params - starting remediation") + rc = self.remediate(client=client, **params) + return rc + + +if __name__ == "__main__": + logging.info("aws_iam_password_policy_min_length.py called - running now") + obj = SetPasswordMinimumLength() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/aws_iam_password_policy_min_length/constraints.txt b/remediation_worker/jobs/aws_iam_password_policy_min_length/constraints.txt new file mode 100644 index 0000000..68a9723 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_policy_min_length/constraints.txt @@ -0,0 +1,43 @@ +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +s3transfer==0.3.4 \ + --hash=sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed \ + --hash=sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +urllib3==1.26.3 \ + --hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \ + --hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73 diff --git a/remediation_worker/jobs/aws_iam_password_policy_min_length/minimum_policy.json b/remediation_worker/jobs/aws_iam_password_policy_min_length/minimum_policy.json new file mode 100644 index 0000000..77586ab --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_policy_min_length/minimum_policy.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "SetMinimumPasswordLength", + "Effect": "Allow", + "Action": [ + "iam:UpdateAccountPasswordPolicy" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/aws_iam_password_policy_min_length/requirements-dev.txt b/remediation_worker/jobs/aws_iam_password_policy_min_length/requirements-dev.txt new file mode 100644 index 0000000..cf03a93 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_policy_min_length/requirements-dev.txt @@ -0,0 +1,9 @@ +-r requirements.txt +-c constraints.txt + +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/aws_iam_password_policy_min_length/requirements.txt b/remediation_worker/jobs/aws_iam_password_policy_min_length/requirements.txt new file mode 100644 index 0000000..ae343fd --- /dev/null +++ b/remediation_worker/jobs/aws_iam_password_policy_min_length/requirements.txt @@ -0,0 +1,6 @@ +boto3==1.16.60 \ + --hash=sha256:10e8d9b18a8ae15677e850c7240140b9539635a03098f01dfdd75b2042d15862 \ + --hash=sha256:aee742f2a2315244fb31a507f65d8809fcd0029508c0b12be8611ddd2075b666 +botocore==1.19.60 \ + --hash=sha256:423a1a9502bd7bc5db8c6e64f9374f64d8ac18e6b870278a9ff65f59d268cd58 \ + --hash=sha256:80dd615a34c7e2c73606070a9358f7b5c1cb0c9989348306c1c9ddff45bb6ebe diff --git a/test/unit/test_aws_iam_password_policy_min_length.py b/test/unit/test_aws_iam_password_policy_min_length.py new file mode 100644 index 0000000..21adbdf --- /dev/null +++ b/test/unit/test_aws_iam_password_policy_min_length.py @@ -0,0 +1,59 @@ +# 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 mock import Mock + +from remediation_worker.jobs.aws_iam_password_policy_min_length.aws_iam_password_policy_min_length import ( + SetPasswordMinimumLength, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5e103dcc90f363146cd", + "Service": "IAM", + "FindingInfo": { + "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "ObjectId": "account_id", + "ObjectChain": "{\\"cloudAccountId\\":\\"cloud_account_id\\",\\"entityId\\":\\"AWS.IAM.159026902.us-west-2.Key.key_id\\",\\"entityName\\":\\"key_id\\",\\"entityType\\":\\"AWS.IAM.AccountPasswordPolicy\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"156898827089\\",\\"provider\\":\\"AWS\\",\\"region\\":\\"us-west-2\\",\\"service\\":\\"CloudTrail\\", \\"properties\\":[{\\"name\\":\\"KeyState\\",\\"stringV\\":\\"Enabled\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestSetPasswordMinLength(object): + def test_parse_payload(self, valid_payload): + params = SetPasswordMinimumLength().parse(valid_payload) + assert params["account_id"] == "account_id" + + def test_remediate_success(self): + client = Mock() + action = SetPasswordMinimumLength() + assert action.remediate(client, "account_id") == 0 + assert client.update_account_password_policy.call_count == 1 + call_args = client.update_account_password_policy.call_args + password_reuse_policy = call_args[1]["MinimumPasswordLength"] + assert password_reuse_policy == 14 + + def test_remediate_with_exception(self): + client = Mock() + action = SetPasswordMinimumLength() + with pytest.raises(Exception): + assert action.remediate(client, "account_id") From 1c7f19e577494be968ddf27f19083734362e2644 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Fri, 30 Jul 2021 03:01:44 +0530 Subject: [PATCH 06/23] PLA-25430 - Remediation Job to delete expired server certificate (#96) --- .../README.md | 70 ++++++++++++++ .../__init__.py | 0 .../aws_iam_server_certificate_expired.py | 92 +++++++++++++++++++ .../constraints.txt | 43 +++++++++ .../minimum_policy.json | 13 +++ .../requirements-dev.txt | 9 ++ .../requirements.txt | 6 ++ ...test_aws_iam_server_certificate_expired.py | 55 +++++++++++ 8 files changed, 288 insertions(+) create mode 100644 remediation_worker/jobs/aws_iam_server_certificate_expired/README.md create mode 100644 remediation_worker/jobs/aws_iam_server_certificate_expired/__init__.py create mode 100644 remediation_worker/jobs/aws_iam_server_certificate_expired/aws_iam_server_certificate_expired.py create mode 100644 remediation_worker/jobs/aws_iam_server_certificate_expired/constraints.txt create mode 100644 remediation_worker/jobs/aws_iam_server_certificate_expired/minimum_policy.json create mode 100644 remediation_worker/jobs/aws_iam_server_certificate_expired/requirements-dev.txt create mode 100644 remediation_worker/jobs/aws_iam_server_certificate_expired/requirements.txt create mode 100644 test/unit/test_aws_iam_server_certificate_expired.py diff --git a/remediation_worker/jobs/aws_iam_server_certificate_expired/README.md b/remediation_worker/jobs/aws_iam_server_certificate_expired/README.md new file mode 100644 index 0000000..3d1622d --- /dev/null +++ b/remediation_worker/jobs/aws_iam_server_certificate_expired/README.md @@ -0,0 +1,70 @@ +# Delete Expired IAM Server Certificate + +This job deletes expired IAM Server Certificate. + +**NOTE -** Deleting the certificate could have implications for your application if you are using an expired server certificate with Elastic Load Balancing, Cloudfront etc. One has to make configurations at respective services to ensure there is no interruption in application. + +### Applicable Rule + +##### Rule ID: +7fe4eb28-3b82-11eb-adc1-0242ac120002 + +##### Rule Name: +IAM server certificates that are expired should be removed + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `iam:DeleteServerCertificate`. + +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_iam_server_certificate_expired.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_iam_server_certificate_expired/__init__.py b/remediation_worker/jobs/aws_iam_server_certificate_expired/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/aws_iam_server_certificate_expired/aws_iam_server_certificate_expired.py b/remediation_worker/jobs/aws_iam_server_certificate_expired/aws_iam_server_certificate_expired.py new file mode 100644 index 0000000..e302d6d --- /dev/null +++ b/remediation_worker/jobs/aws_iam_server_certificate_expired/aws_iam_server_certificate_expired.py @@ -0,0 +1,92 @@ +# 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 DeleteExpiredServerCertificate(object): + 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) + certificate_name = finding_info.get("ObjectId", None) + + if certificate_name is None: + raise Exception( + "Missing parameters for 'payload.notificationInfo.ObjectId'." + ) + + logging.info("parsed params") + logging.info(f" certificate_name: {certificate_name}") + + return_dict = { + "certificate_name": certificate_name, + } + logging.info(return_dict) + return return_dict + + def remediate(self, client, certificate_name): + """Deleting Expired Server Certificate + :param client: Instance of the AWS boto3 client. + :param certificate_name: Certificate name. + :type client: object. + :type certificate_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: botocore.exceptions.ClientError + """ + try: + logging.info(f"Deleting Expired Server Certificate: {certificate_name}") + logging.info("executing client.delete_server_certificate") + client.delete_server_certificate(ServerCertificateName=certificate_name) + logging.info("successfully completed remediation job") + except Exception as e: + logging.error(f"{str(e)}") + raise + 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 = self.parse(args[1]) + client = boto3.client("iam") + logging.info("acquired iam client and parsed params - starting remediation") + rc = self.remediate(client=client, **params) + return rc + + +if __name__ == "__main__": + logging.info("aws_iam_server_certificate_expired.py called - running now") + obj = DeleteExpiredServerCertificate() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/aws_iam_server_certificate_expired/constraints.txt b/remediation_worker/jobs/aws_iam_server_certificate_expired/constraints.txt new file mode 100644 index 0000000..68a9723 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_server_certificate_expired/constraints.txt @@ -0,0 +1,43 @@ +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +s3transfer==0.3.4 \ + --hash=sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed \ + --hash=sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +urllib3==1.26.3 \ + --hash=sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80 \ + --hash=sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73 diff --git a/remediation_worker/jobs/aws_iam_server_certificate_expired/minimum_policy.json b/remediation_worker/jobs/aws_iam_server_certificate_expired/minimum_policy.json new file mode 100644 index 0000000..683f089 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_server_certificate_expired/minimum_policy.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DeleteExpiredServerCertificate", + "Effect": "Allow", + "Action": [ + "iam:DeleteServerCertificate" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/aws_iam_server_certificate_expired/requirements-dev.txt b/remediation_worker/jobs/aws_iam_server_certificate_expired/requirements-dev.txt new file mode 100644 index 0000000..cf03a93 --- /dev/null +++ b/remediation_worker/jobs/aws_iam_server_certificate_expired/requirements-dev.txt @@ -0,0 +1,9 @@ +-r requirements.txt +-c constraints.txt + +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/aws_iam_server_certificate_expired/requirements.txt b/remediation_worker/jobs/aws_iam_server_certificate_expired/requirements.txt new file mode 100644 index 0000000..ae343fd --- /dev/null +++ b/remediation_worker/jobs/aws_iam_server_certificate_expired/requirements.txt @@ -0,0 +1,6 @@ +boto3==1.16.60 \ + --hash=sha256:10e8d9b18a8ae15677e850c7240140b9539635a03098f01dfdd75b2042d15862 \ + --hash=sha256:aee742f2a2315244fb31a507f65d8809fcd0029508c0b12be8611ddd2075b666 +botocore==1.19.60 \ + --hash=sha256:423a1a9502bd7bc5db8c6e64f9374f64d8ac18e6b870278a9ff65f59d268cd58 \ + --hash=sha256:80dd615a34c7e2c73606070a9358f7b5c1cb0c9989348306c1c9ddff45bb6ebe diff --git a/test/unit/test_aws_iam_server_certificate_expired.py b/test/unit/test_aws_iam_server_certificate_expired.py new file mode 100644 index 0000000..18e83a2 --- /dev/null +++ b/test/unit/test_aws_iam_server_certificate_expired.py @@ -0,0 +1,55 @@ +# 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 mock import Mock + +from remediation_worker.jobs.aws_iam_server_certificate_expired.aws_iam_server_certificate_expired import ( + DeleteExpiredServerCertificate, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5e103dcc90f363146cd", + "Service": "IAM", + "FindingInfo": { + "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "ObjectId": "certificate_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"cloud_account_id\\",\\"entityId\\":\\"AWS.IAM.159026902.us-west-2.Key.key_id\\",\\"entityName\\":\\"key_id\\",\\"entityType\\":\\"AWS.IAM.AccountPasswordPolicy\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"156898827089\\",\\"provider\\":\\"AWS\\",\\"region\\":\\"us-west-2\\",\\"service\\":\\"CloudTrail\\", \\"properties\\":[{\\"name\\":\\"KeyState\\",\\"stringV\\":\\"Enabled\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestDeleteExpiredServerCertificate(object): + def test_parse_payload(self, valid_payload): + params = DeleteExpiredServerCertificate().parse(valid_payload) + assert params["certificate_name"] == "certificate_name" + + def test_remediate_success(self): + client = Mock() + action = DeleteExpiredServerCertificate() + assert action.remediate(client, "certificate_name") == 0 + + def test_remediate_with_exception(self): + client = Mock() + action = DeleteExpiredServerCertificate() + with pytest.raises(Exception): + assert action.remediate(client, "certificate_name") From 09f6aaa75596045f3a29892b37fb59c9f95f1b2f Mon Sep 17 00:00:00 2001 From: lytran2000 <44222483+lytran2000@users.noreply.github.com> Date: Thu, 29 Jul 2021 14:33:10 -0700 Subject: [PATCH 07/23] Initial commit for kinesis_encrypt_stream (#97) * Initial commit for kinesis_encrypt_stream * modified to add a return and exception to kinesis_encrypt_stream.py and unit testcases for remediate * remove print * update README.md * update README.md * remove format in kinesis_encrypt_stream.py * update README with a correct instruction to run the script and add a missing error loggin Co-authored-by: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> --- README.md | 23 ++- .../jobs/kinesis_encrypt_stream/README.md | 77 +++++++ .../jobs/kinesis_encrypt_stream/__init__.py | 0 .../kinesis_encrypt_stream/constraints.txt | 43 ++++ .../kinesis_encrypt_stream.py | 188 ++++++++++++++++++ .../minimum_policy.json | 14 ++ .../requirements-dev.txt | 6 + .../kinesis_encrypt_stream/requirements.txt | 6 + test/unit/test_kinesis_encrypt_stream.py | 148 ++++++++++++++ tox.ini | 7 + 10 files changed, 501 insertions(+), 11 deletions(-) create mode 100644 remediation_worker/jobs/kinesis_encrypt_stream/README.md create mode 100644 remediation_worker/jobs/kinesis_encrypt_stream/__init__.py create mode 100644 remediation_worker/jobs/kinesis_encrypt_stream/constraints.txt create mode 100644 remediation_worker/jobs/kinesis_encrypt_stream/kinesis_encrypt_stream.py create mode 100644 remediation_worker/jobs/kinesis_encrypt_stream/minimum_policy.json create mode 100644 remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt create mode 100644 remediation_worker/jobs/kinesis_encrypt_stream/requirements.txt create mode 100644 test/unit/test_kinesis_encrypt_stream.py diff --git a/README.md b/README.md index c17afa2..4c07680 100644 --- a/README.md +++ b/README.md @@ -134,17 +134,18 @@ The table below lists all the supported jobs with their links. | 21. | 688d093c-3b8d-11eb-adc1-0242ac120002 | S3 bucket should allow only HTTPS requests | [aws-s3-bucket-policy-allow-https](remediation_worker/jobs/aws_s3_bucket_policy_allow_https) | | 22. | 09639b9d-98e8-493b-b8a4-916775a7dea9 | SQS queue policy should restricted access to required users | [aws-sqs-queue-publicly-accessible](remediation_worker/jobs/aws_sqs_queue_publicly_accessible) | | 23. | 1ec4a1f2-3e08-11eb-b378-0242ac130002 | Network ACL should restrict administration ports (3389 and 22) from public access | [aws-ec2-administration-ports-ingress-allowed](remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed) | -| 24. | 5c8c263d7a550e1fb6560c39 | EC2 instance should restrict public access to FTP data port (20) | [ec2-close-port-20](remediation_worker/jobs/ec2_close_port_20) | -| 25. | 4823ede0-7bed-4af0-a182-81c2ada80203 | EC2 instance should restrict public access to Kibana (5601) | [ec2-close-port-5601](remediation_worker/jobs/ec2_close_port_5601) | -| 26. | 5c8c26427a550e1fb6560c41 | EC2 instance should restrict public access to MySQL server port (3306) | [ec2-close-port-3306](remediation_worker/jobs/ec2_close_port_3306) | -| 27. | 5c8c26417a550e1fb6560c3e | EC2 instance should restrict public access to Oracle SQL port (1521) | [ec2-close-port-1521](remediation_worker/jobs/ec2_close_port_1521) | -| 28. | 5c8c26417a550e1fb6560c3d | EC2 instance should restrict public access to SQL Server port (1433) | [ec2-close-port-1433](remediation_worker/jobs/ec2_close_port_1433) | -| 29. | 5c8c263e7a550e1fb6560c3b | EC2 instance should restrict public access to Telnet port (23) | [ec2-close-port-23](remediation_worker/jobs/ec2_close_port_23) | -| 30. | 5c8c263d7a550e1fb6560c3a | EC2 instance should restrict public access to FTP port (21) | [ec2-close-port-21](remediation_worker/jobs/ec2_close_port_21) | -| 31. | 04700175-adbe-49e1-bc7a-bc9605597ce2 | EC2 instance should restrict public access to Elasticsearch ports (9200,9300) | [ec2-close-port-9200_9300](remediation_worker/jobs/ec2_close_port_9200_9300) | -| 32. | 5c8c26427a550e1fb6560c40 | EC2 instance should restrict public access to MongoDB port (27017) | [ec2-close-port-27017](remediation_worker/jobs/ec2_close_port_27017) | -| 33. | 5c8c26407a550e1fb6560c3c | EC2 instance should restrict public access to TCP port (8080) | [ec2-close-port-8080](remediation_worker/jobs/ec2_close_port_8080) | -| 34. | 5c8c26447a550e1fb6560c44 | EC2 instance should restrict public access to Redshift port (5439) | [ec2-close-port-5439](remediation_worker/jobs/ec2_close_port_5439) | +| 24. | ce603728-d631-4bae-8657-c22da6e5944e | Kinesis data stream should be encrypted | [kinesis-encrypt-stream](remediation_worker/jobs/kinesis_encrypt_stream) | +| 25. | 5c8c263d7a550e1fb6560c39 | EC2 instance should restrict public access to FTP data port (20) | [ec2-close-port-20](remediation_worker/jobs/ec2_close_port_20) | +| 26. | 4823ede0-7bed-4af0-a182-81c2ada80203 | EC2 instance should restrict public access to Kibana (5601) | [ec2-close-port-5601](remediation_worker/jobs/ec2_close_port_5601) | +| 27. | 5c8c26427a550e1fb6560c41 | EC2 instance should restrict public access to MySQL server port (3306) | [ec2-close-port-3306](remediation_worker/jobs/ec2_close_port_3306) | +| 28. | 5c8c26417a550e1fb6560c3e | EC2 instance should restrict public access to Oracle SQL port (1521) | [ec2-close-port-1521](remediation_worker/jobs/ec2_close_port_1521) | +| 29. | 5c8c26417a550e1fb6560c3d | EC2 instance should restrict public access to SQL Server port (1433) | [ec2-close-port-1433](remediation_worker/jobs/ec2_close_port_1433) | +| 30. | 5c8c263e7a550e1fb6560c3b | EC2 instance should restrict public access to Telnet port (23) | [ec2-close-port-23](remediation_worker/jobs/ec2_close_port_23) | +| 31. | 5c8c263d7a550e1fb6560c3a | EC2 instance should restrict public access to FTP port (21) | [ec2-close-port-21](remediation_worker/jobs/ec2_close_port_21) | +| 32. | 04700175-adbe-49e1-bc7a-bc9605597ce2 | EC2 instance should restrict public access to Elasticsearch ports (9200,9300) | [ec2-close-port-9200_9300](remediation_worker/jobs/ec2_close_port_9200_9300) | +| 33. | 5c8c26427a550e1fb6560c40 | EC2 instance should restrict public access to MongoDB port (27017) | [ec2-close-port-27017](remediation_worker/jobs/ec2_close_port_27017) | +| 34. | 5c8c26407a550e1fb6560c3c | EC2 instance should restrict public access to TCP port (8080) | [ec2-close-port-8080](remediation_worker/jobs/ec2_close_port_8080) | +| 35. | 5c8c26447a550e1fb6560c44 | EC2 instance should restrict public access to Redshift port (5439) | [ec2-close-port-5439](remediation_worker/jobs/ec2_close_port_5439) | ## 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/kinesis_encrypt_stream/README.md b/remediation_worker/jobs/kinesis_encrypt_stream/README.md new file mode 100644 index 0000000..bec73bc --- /dev/null +++ b/remediation_worker/jobs/kinesis_encrypt_stream/README.md @@ -0,0 +1,77 @@ +# Encrypt Kinesis data stream + +This job encrypts the Kinesis data stream. + +## Getting Started + +##### Rule ID: +ce603728-d631-4bae-8657-c22da6e5944e + +##### Rule Name: +Kinesis data stream should be encrypted + +### Prerequisites + +The provided AWS credential must have access to the Kinesis data stream. + +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 kinesis_encrypt_stream.py "`cat finding.json`" +``` +where finding.json has kinesis data stream and region info: +```json +{ + "notificationInfo": { + "FindingInfo": { + "ObjectId": "kinesis-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/kinesis_encrypt_stream/__init__.py b/remediation_worker/jobs/kinesis_encrypt_stream/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/kinesis_encrypt_stream/constraints.txt b/remediation_worker/jobs/kinesis_encrypt_stream/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/kinesis_encrypt_stream/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/kinesis_encrypt_stream/kinesis_encrypt_stream.py b/remediation_worker/jobs/kinesis_encrypt_stream/kinesis_encrypt_stream.py new file mode 100644 index 0000000..0141569 --- /dev/null +++ b/remediation_worker/jobs/kinesis_encrypt_stream/kinesis_encrypt_stream.py @@ -0,0 +1,188 @@ +# 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 KinesisEncryptStream(): + 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) + + stream_name = finding_info.get("ObjectId", None) + region = finding_info.get("Region", None) + + if stream_name is None: + logging.error("Missing parameters for 'STREAM_NAME'.") + raise Exception("Missing parameters for 'stream_name'.") + + if region is None: + logging.error("Missing parameters for 'REGION'.") + raise Exception("Missing parameters for 'REGION'.") + + logging.info("parsed params:") + logging.info(" stream_name: %s", stream_name) + logging.info(" region: %s", region) + + return { + "stream_name": stream_name, + "region": region + } + + def remediate(self, stream_name, client, region): + """Encrypts unencrypted kinesis data streams. + Args: + stream_name ([str]): The name of the kinesis stream. + client: Instance of the AWS boto3 client. + """ + + logging.info( + "Beginning kinesis data stream encryption evaluation." + ) + + # Check to see if kinesis data stream is encrypted + encryption_type = self.check_encryption(client, stream_name) + + # If kinesis data stream is not encrypted + if encryption_type == "NONE": + res = self.enable_encryption(client, stream_name) + return res + # If encryption is enabled + # We have nothing to do. + # log and exit + elif encryption_type == "KMS": + logging.info( + "Kinesis data stream %s is already encrypted, taking no action.", + stream_name + ) + return 0 + # If anything else returns, + # log and exit + else: + error = "An unknown encryption type {0} returned for kinesis data stream {1}. Exiting".format(encryption_type, stream_name) + logging.error(error) + return 1 + + def check_encryption(self, client, stream_name): + """Checks the kinesis data stream encryption setting. + Args: + client: Instance of the AWS boto3 client + stream_name ([str]): The name of the kinesis stream. + Returns: + [string]: "NONE" if no encryption is enabled. + """ + + # Check if stream is encrypted + try: + encryption_type = client.describe_stream( + StreamName=stream_name)["StreamDescription"]["EncryptionType"] + return encryption_type + + except ClientError as user_error: + if user_error.response["Error"]["Code"] == "ResourceNotFoundException": + logging.error( + "A failure occured with error: %s. Check if the resource exists.", user_error.response['Error']['Code'] + ) + elif user_error.response["Error"]["Code"] == "LimitExceededException": + error = "Receiving LimitExceededException exception: {0} while trying to encrypt kinesis data stream {1}".format(stream_name,user_error.response["Error"]["Code"]) + logging.error(error) + + + except Exception as e: + error = "Receiving other exceptions {0} reading kinesis data stream {1}".format(str(e), stream_name) + logging.error(error) + + return "" + + def enable_encryption(self, client, stream_name): + """Enable encryption on the kinesis data stream. + Args: + client: Instance of the AWS boto3 client. + stream_name ([str]): The name of the kinesis stream. + """ + logging.info( + "Enabling encryption on kinesis data stream %s", + stream_name + ) + try: + client.start_stream_encryption( + StreamName=stream_name, + EncryptionType="KMS", + KeyId="alias/aws/kinesis" # KEK owned by Kinesis Data Streams + ) + logging.info( + "Encryption enabled on kinesis data stream %s", + stream_name + ) + return 0 + except ClientError as user_error: + if user_error.response["Error"]["Code"] == "ResourceInUseException": + logging.error( + "Kinesis data stream %s is in an unavailable state.", + stream_name + ) + else: + error = "Receiving Kinesis exception: {0} while tyring to encrypt kinesis data stream {1}".format(stream_name,user_error.response["Error"]["Code"]) + logging.error(error) + return 1 + except Exception as e: + error = "Receiving exception {0} for kinesis data stream {1}".format(str(e), stream_name) + logging.error(error) + return 1 + + + 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("kinesis", region_name=params['region']) + + logging.info( + "Acquired kinesis client and parsed params - starting remediation." + ) + + return self.remediate(client=client, **params) + + +if __name__ == "__main__": + logging.info( + "kinesis_encrypt_stream.py called - running now %s", + sys.argv[0] + ) + obj = KinesisEncryptStream() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/kinesis_encrypt_stream/minimum_policy.json b/remediation_worker/jobs/kinesis_encrypt_stream/minimum_policy.json new file mode 100644 index 0000000..e3bf2b9 --- /dev/null +++ b/remediation_worker/jobs/kinesis_encrypt_stream/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Kinesis", + "Effect": "Allow", + "Action": [ + "Kinesis:DescribeStream", + "Kinesis:StartStreamEncryption" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt b/remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/kinesis_encrypt_stream/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/kinesis_encrypt_stream/requirements.txt b/remediation_worker/jobs/kinesis_encrypt_stream/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/kinesis_encrypt_stream/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_kinesis_encrypt_stream.py b/test/unit/test_kinesis_encrypt_stream.py new file mode 100644 index 0000000..b8659b7 --- /dev/null +++ b/test/unit/test_kinesis_encrypt_stream.py @@ -0,0 +1,148 @@ +# 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.kinesis_encrypt_stream.kinesis_encrypt_stream import KinesisEncryptStream + +@pytest.fixture +def valid_payload(): + return """ { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "kinesis-stream", + "Region": "us-east-1" + } + } + } +""" + + +class TestKinesisEncryptStream: + def test_parse_payload(self, valid_payload): + obj = KinesisEncryptStream() + param = obj.parse(valid_payload) + assert "stream_name" in param + assert param["stream_name"] == "kinesis-stream" + assert "region" in param + assert param["region"] == "us-east-1" + + def test_remediate_success(self): + class TestClient(object): + def start_stream_encryption(self, **kwargs): + return 0 + + def describe_stream(self, **kwargs): + stream = {'StreamDescription': {'EncryptionType':"NONE"}} + return stream + + client = TestClient() + obj = KinesisEncryptStream() + assert obj.remediate("stream_name", client, "us-west1") == 0 + + def test_remediate_not_success_resource_not_found(self): + class TestClient(object): + def start_stream_encryption(self, **kwargs): + return 0 + + def describe_stream(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "ResourceNotFoundException", + "Message": "ResourceNotFoundException for reason", + } + }, + "KinesisEncryptStream", + ) + + client = TestClient() + obj = KinesisEncryptStream() + assert obj.remediate("stream_name", client, "us-west1") == 1 + + def test_remediate_not_success_limit_exceed(self): + class TestClient(object): + def start_stream_encryption(self, **kwargs): + return 0 + + def describe_stream(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "LimitExceededException", + "Message": "LimitExceededException for reason", + } + }, + "KinesisEncryptStream", + ) + + client = TestClient() + obj = KinesisEncryptStream() + assert obj.remediate("stream_name", client, "us-west1") == 1 + + def test_remediate_not_success_resource_in_use(self): + class TestClient(object): + def describe_stream(self, **kwargs): + stream = {'StreamDescription': {'EncryptionType':"NONE"}} + return stream + + def start_stream_encryption(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "ResourceInUseException", + "Message": "ResourceInUseException for reason", + } + }, + "KinesisEncryptStream", + ) + + client = TestClient() + obj = KinesisEncryptStream() + assert obj.remediate("stream_name", client, "us-west1") == 1 + + def test_remediate_not_success_other_aws_exceptions(self): + class TestClient(object): + def describe_stream(self, **kwargs): + stream = {'StreamDescription': {'EncryptionType':"NONE"}} + return stream + + def start_stream_encryption(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "InvalidArgumentException", + "Message": "InvalidArgumentException for reason", + } + }, + "KinesisEncryptStream", + ) + + client = TestClient() + obj = KinesisEncryptStream() + assert obj.remediate("stream_name", client, "us-west1") == 1 + + def test_remediate_not_success_other_exceptions(self): + class TestClient(object): + def describe_stream(self, **kwargs): + stream = {'StreamDescription': {'EncryptionType':"NONE"}} + return stream + + def start_stream_encryption(self, **kwargs): + raise Exception('This is the exception you expect to handle') + + client = TestClient() + obj = KinesisEncryptStream() + assert obj.remediate("stream_name", client, "us-west1") == 1 + diff --git a/tox.ini b/tox.ini index 8124eba..5b2ac50 100644 --- a/tox.ini +++ b/tox.ini @@ -52,6 +52,7 @@ envlist = unit-azure-postgresql-allow-access-to-azure-service-disabled unit-aws-s3-bucket-policy-allow-https unit-aws-sqs-queue-publicly-accessible + unit-kinesis-encrypt-stream [testenv] @@ -306,6 +307,12 @@ changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_sqs_queue_publicly_accessible.py deps = -r remediation_worker/jobs/aws_sqs_queue_publicly_accessible/requirements-dev.txt +[testenv:unit-kinesis-encrypt-stream] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_kinesis_encrypt_stream.py +deps = -r remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt + [testenv:unit-ec2-close-port-5601] description = Unit test the project changedir = test From 80e9588ed6c01f9eef008af9d29eb5e3a4953d4e Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Fri, 30 Jul 2021 03:04:25 +0530 Subject: [PATCH 08/23] PLA-26855 - Updated azure remediation jobs to wait for the poller result (#99) * PLA-26855 - Updated azure remediation jobs to wait for the poller result * PLA-26855 - Update azure jobs to poll continuously and log the status --- .../README.md | 2 +- .../minimum_policy.json | 4 +++- .../azure_mysql_enforce_ssl_connection_enable.py | 8 +++++++- ...e_postgresql_allow_access_to_azure_service_disabled.py | 8 +++++++- .../azure_postgresql_enforce_ssl_connection_enable.py | 8 +++++++- .../azure_sql_auditing_on_server.py | 7 ++++++- .../jobs/azure_sql_tde_protector_encrypted_cmk/README.md | 1 + .../azure_sql_tde_protector_encrypted_cmk.py | 8 +++++++- .../minimum_permissions.json | 1 + .../azure_sql_threat_detection_on_server.py | 8 +++++++- .../azure_sql_threat_detection_types_all_server.py | 8 +++++++- test/unit/test_azure_sql_auditing_on_server.py | 5 ++++- 12 files changed, 58 insertions(+), 10 deletions(-) diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md index e9f33e1..9b512a8 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md @@ -14,7 +14,7 @@ Network ACL should restrict administration ports (3389 and 22) from public acces ### Prerequisites -The provided AWS credential must have access to `ec2:DeleteNetworkAcl`, `ec2:DescribeNetworkAcls` and `ec2:ReplaceNetworkAclEntry`. +The provided AWS credential must have access to `ec2:CreateNetworkAclEntry`, `ec2:DeleteNetworkAclEntry`, `DescribeNetworkAcls` and `ec2:ReplaceNetworkAclEntry`. You may find the latest example policy file [here](minimum_policy.json) diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json index ba9b8ba..b3a9b8d 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json @@ -5,8 +5,10 @@ "Sid": "RemoveAdministrationPortsIngress", "Effect": "Allow", "Action": [ + "ec2:CreateNetworkAclEntry", "ec2:DescribeNetworkAcls", - "ec2:DeleteNetworkAcl" + "ec2:DeleteNetworkAclEntry", + "ec2:ReplaceNetworkAclEntry" ], "Resource": "*" } diff --git a/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py b/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py index d0d1512..c6366c1 100644 --- a/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py +++ b/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py @@ -77,11 +77,17 @@ def remediate(self, client, resource_group_name, mysql_server_name): logging.info(f" resource_group_name={resource_group_name}") logging.info(f" server_name={mysql_server_name}") - client.servers.begin_update( + poller = client.servers.begin_update( resource_group_name=resource_group_name, server_name=mysql_server_name, parameters=ServerUpdateParameters(ssl_enforcement="Enabled"), ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() + except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py b/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py index ca2725c..084ff74 100644 --- a/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py +++ b/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py @@ -70,11 +70,17 @@ def remediate(self, client, resource_group_name, postgre_server_name): logging.info(f" server_name={postgre_server_name}") logging.info(f" firewall_rule_name=AllowAllWindowsAzureIps") - client.firewall_rules.begin_delete( + poller = client.firewall_rules.begin_delete( resource_group_name=resource_group_name, server_name=postgre_server_name, firewall_rule_name="AllowAllWindowsAzureIps", ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() + except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py b/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py index 3b530f2..e0f0fb0 100644 --- a/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py +++ b/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py @@ -70,11 +70,17 @@ def remediate(self, client, resource_group_name, postgre_server_name): logging.info(f" resource_group_name={resource_group_name}") logging.info(f" server_name={postgre_server_name}") - client.servers.begin_update( + poller = client.servers.begin_update( resource_group_name=resource_group_name, server_name=postgre_server_name, parameters=ServerUpdateParameters(ssl_enforcement="Enabled"), ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() + except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index 2de4793..a90691f 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -571,7 +571,7 @@ def create_server_blob_auditing_policy( logging.info(f" resource_group_name={resource_group_name}") logging.info(f" server_name={sql_server_name}") - client.server_blob_auditing_policies.create_or_update( + poller = client.server_blob_auditing_policies.create_or_update( resource_group_name=resource_group_name, server_name=sql_server_name, parameters=ServerBlobAuditingPolicy( @@ -579,6 +579,11 @@ def create_server_blob_auditing_policy( storage_endpoint=f"https://{stg_account_name}.blob.core.windows.net/", ), ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() def ensure_identity_assigned( self, client, resource_group_name, sql_server_name, region diff --git a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/README.md b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/README.md index ca1841e..28667b0 100644 --- a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/README.md +++ b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/README.md @@ -27,6 +27,7 @@ The provided Azure service principal must have the following permissions: `Microsoft.KeyVault/vaults/accessPolicies/write`, `Microsoft.Sql/servers/read`, `Microsoft.Sql/servers/write`, +`Microsoft.Sql/servers/encryptionProtector/read`, `Microsoft.Sql/servers/encryptionProtector/write`, `Microsoft.Sql/servers/keys/write` `Microsoft.Sql/servers/keys/read`. diff --git a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py index 2138dce..f7c437f 100644 --- a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py +++ b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py @@ -688,7 +688,7 @@ def remediate( ).result() # Update the SQL TDE protector to encrypt using cmk - client.encryption_protectors.begin_create_or_update( + poller = client.encryption_protectors.begin_create_or_update( resource_group_name=resource_group_name, server_name=sql_server_name, encryption_protector_name="current", @@ -696,6 +696,12 @@ def remediate( server_key_name=server_key_name, server_key_type="AzureKeyVault" ), ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() + except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/minimum_permissions.json b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/minimum_permissions.json index 38866a6..aaf427f 100644 --- a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/minimum_permissions.json +++ b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/minimum_permissions.json @@ -19,6 +19,7 @@ "Microsoft.KeyVault/vaults/accessPolicies/write", "Microsoft.Sql/servers/read", "Microsoft.Sql/servers/write", + "Microsoft.Sql/servers/encryptionProtector/read", "Microsoft.Sql/servers/encryptionProtector/write", "Microsoft.Sql/servers/keys/write", "Microsoft.Sql/servers/keys/read" diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py b/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py index 58742e4..f1f9f31 100644 --- a/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py @@ -70,13 +70,19 @@ def remediate(self, client, resource_group_name, sql_server_name): logging.info(f" resource_group_name={resource_group_name}") logging.info(f" server_name={sql_server_name}") - client.server_security_alert_policies.create_or_update( + poller = client.server_security_alert_policies.create_or_update( resource_group_name=resource_group_name, server_name=sql_server_name, parameters=ServerSecurityAlertPolicy( state=SecurityAlertPolicyState.enabled ), ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() + except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py b/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py index a383c68..292155d 100644 --- a/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py +++ b/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py @@ -73,13 +73,19 @@ def remediate(self, client, resource_group_name, sql_server_name): logging.info(f" resource_group_name={resource_group_name}") logging.info(f" server_name={sql_server_name}") - client.server_security_alert_policies.create_or_update( + poller = client.server_security_alert_policies.create_or_update( resource_group_name=resource_group_name, server_name=sql_server_name, parameters=ServerSecurityAlertPolicy( state=SecurityAlertPolicyState.enabled, disabled_alerts=[] ), ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() + except Exception as e: logging.error(f"{str(e)}") raise diff --git a/test/unit/test_azure_sql_auditing_on_server.py b/test/unit/test_azure_sql_auditing_on_server.py index 05c93f0..0bbe543 100644 --- a/test/unit/test_azure_sql_auditing_on_server.py +++ b/test/unit/test_azure_sql_auditing_on_server.py @@ -77,6 +77,7 @@ def test_remediate_without_stg_without_keyvault(self): action.ensure_identity_assigned = Mock() action.create_role_assignment = Mock() action.create_server_blob_auditing_policy = Mock() + action.check_role_assignment = Mock() identity = Identity( principal_id="139bcf82-e14e-4773-bcf4-1da136674792", @@ -106,6 +107,7 @@ def test_remediate_without_stg_without_keyvault(self): location="eastus", identity=identity, ) + action.check_role_assignment.return_value = True action.check_stg_account.return_value = None action.check_key_vault.return_value = None assert ( @@ -134,7 +136,6 @@ def test_remediate_without_stg_without_keyvault(self): assert action.create_storage_account.call_count == 1 assert action.create_key_vault.call_count == 1 assert action.create_diagnostic_setting.call_count == 1 - assert action.create_role_assignment.call_count == 1 assert action.create_server_blob_auditing_policy.call_count == 1 def test_remediate_without_stg_with_keyvault(self): @@ -159,6 +160,7 @@ def test_remediate_without_stg_with_keyvault(self): action.ensure_identity_assigned = Mock() action.create_role_assignment = Mock() action.create_server_blob_auditing_policy = Mock() + action.check_role_assignment = Mock() identity = Identity( principal_id="139bcf82-e14e-4773-bcf4-1da136674792", @@ -189,6 +191,7 @@ def test_remediate_without_stg_with_keyvault(self): vault_uri="https://stg-keyvault-rem.vault.azure.net", ), ) + action.check_role_assignment.return_value = False assert ( action.remediate( client_id, From a7300a725a8cbf8b888d8ff4698212e95fdfcafb Mon Sep 17 00:00:00 2001 From: lytran2000 <44222483+lytran2000@users.noreply.github.com> Date: Tue, 3 Aug 2021 08:06:06 -0700 Subject: [PATCH 09/23] =?UTF-8?q?Initial=20commit=20for=20aws=203=20jobs:?= =?UTF-8?q?=20ebs=5Fprivate=5Fsnapshot,=20rds=5Fenable=5Fversi=E2=80=A6=20?= =?UTF-8?q?(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_version_update, rds_remove_public_endpoint * Update ebs_private_snapshot.py * Incorporated comments and inputs from PR review * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md --- README.md | 5 +- .../jobs/ebs_private_snapshot/README.md | 75 ++++++++ .../jobs/ebs_private_snapshot/__init__.py | 0 .../jobs/ebs_private_snapshot/constraints.txt | 43 +++++ .../ebs_private_snapshot.py | 132 +++++++++++++++ .../ebs_private_snapshot/minimum_policy.json | 14 ++ .../ebs_private_snapshot/requirements-dev.txt | 6 + .../ebs_private_snapshot/requirements.txt | 6 + .../jobs/rds_enable_version_update/README.md | 75 ++++++++ .../rds_enable_version_update/__init__.py | 0 .../rds_enable_version_update/constraints.txt | 43 +++++ .../minimum_policy.json | 14 ++ .../rds_enable_version_update.py | 121 +++++++++++++ .../requirements-dev.txt | 6 + .../requirements.txt | 6 + .../jobs/rds_remove_public_endpoint/README.md | 74 ++++++++ .../rds_remove_public_endpoint/__init__.py | 0 .../constraints.txt | 43 +++++ .../minimum_policy.json | 14 ++ .../rds_remove_public_endpoint.py | 160 ++++++++++++++++++ .../requirements-dev.txt | 6 + .../requirements.txt | 6 + test/unit/test_ebs_private_snapshot.py | 94 ++++++++++ test/unit/test_rds_enable_version_update.py | 76 +++++++++ test/unit/test_rds_remove_public_endpoint.py | 78 +++++++++ tox.ini | 24 ++- 26 files changed, 1119 insertions(+), 2 deletions(-) create mode 100644 remediation_worker/jobs/ebs_private_snapshot/README.md create mode 100644 remediation_worker/jobs/ebs_private_snapshot/__init__.py create mode 100644 remediation_worker/jobs/ebs_private_snapshot/constraints.txt create mode 100644 remediation_worker/jobs/ebs_private_snapshot/ebs_private_snapshot.py create mode 100644 remediation_worker/jobs/ebs_private_snapshot/minimum_policy.json create mode 100644 remediation_worker/jobs/ebs_private_snapshot/requirements-dev.txt create mode 100644 remediation_worker/jobs/ebs_private_snapshot/requirements.txt create mode 100644 remediation_worker/jobs/rds_enable_version_update/README.md create mode 100644 remediation_worker/jobs/rds_enable_version_update/__init__.py create mode 100644 remediation_worker/jobs/rds_enable_version_update/constraints.txt create mode 100644 remediation_worker/jobs/rds_enable_version_update/minimum_policy.json create mode 100644 remediation_worker/jobs/rds_enable_version_update/rds_enable_version_update.py create mode 100644 remediation_worker/jobs/rds_enable_version_update/requirements-dev.txt create mode 100644 remediation_worker/jobs/rds_enable_version_update/requirements.txt create mode 100644 remediation_worker/jobs/rds_remove_public_endpoint/README.md create mode 100644 remediation_worker/jobs/rds_remove_public_endpoint/__init__.py create mode 100644 remediation_worker/jobs/rds_remove_public_endpoint/constraints.txt create mode 100644 remediation_worker/jobs/rds_remove_public_endpoint/minimum_policy.json create mode 100644 remediation_worker/jobs/rds_remove_public_endpoint/rds_remove_public_endpoint.py create mode 100644 remediation_worker/jobs/rds_remove_public_endpoint/requirements-dev.txt create mode 100644 remediation_worker/jobs/rds_remove_public_endpoint/requirements.txt create mode 100644 test/unit/test_ebs_private_snapshot.py create mode 100644 test/unit/test_rds_enable_version_update.py create mode 100644 test/unit/test_rds_remove_public_endpoint.py diff --git a/README.md b/README.md index 4c07680..8e31468 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ The table below lists all the supported jobs with their links. | 21. | 688d093c-3b8d-11eb-adc1-0242ac120002 | S3 bucket should allow only HTTPS requests | [aws-s3-bucket-policy-allow-https](remediation_worker/jobs/aws_s3_bucket_policy_allow_https) | | 22. | 09639b9d-98e8-493b-b8a4-916775a7dea9 | SQS queue policy should restricted access to required users | [aws-sqs-queue-publicly-accessible](remediation_worker/jobs/aws_sqs_queue_publicly_accessible) | | 23. | 1ec4a1f2-3e08-11eb-b378-0242ac130002 | Network ACL should restrict administration ports (3389 and 22) from public access | [aws-ec2-administration-ports-ingress-allowed](remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed) | -| 24. | ce603728-d631-4bae-8657-c22da6e5944e | Kinesis data stream should be encrypted | [kinesis-encrypt-stream](remediation_worker/jobs/kinesis_encrypt_stream) | +| 24. | ce603728-d631-4bae-8657-c22da6e5944e | Kinesis data stream should be encrypted | 25. | 5c8c263d7a550e1fb6560c39 | EC2 instance should restrict public access to FTP data port (20) | [ec2-close-port-20](remediation_worker/jobs/ec2_close_port_20) | | 26. | 4823ede0-7bed-4af0-a182-81c2ada80203 | EC2 instance should restrict public access to Kibana (5601) | [ec2-close-port-5601](remediation_worker/jobs/ec2_close_port_5601) | | 27. | 5c8c26427a550e1fb6560c41 | EC2 instance should restrict public access to MySQL server port (3306) | [ec2-close-port-3306](remediation_worker/jobs/ec2_close_port_3306) | @@ -146,6 +146,9 @@ The table below lists all the supported jobs with their links. | 33. | 5c8c26427a550e1fb6560c40 | EC2 instance should restrict public access to MongoDB port (27017) | [ec2-close-port-27017](remediation_worker/jobs/ec2_close_port_27017) | | 34. | 5c8c26407a550e1fb6560c3c | EC2 instance should restrict public access to TCP port (8080) | [ec2-close-port-8080](remediation_worker/jobs/ec2_close_port_8080) | | 35. | 5c8c26447a550e1fb6560c44 | EC2 instance should restrict public access to Redshift port (5439) | [ec2-close-port-5439](remediation_worker/jobs/ec2_close_port_5439) | +| 36. | 2cdb8877-7ac3-4483-9ed0-1e792171d125 | EBS volume snapshot should be private | [ebs-private-snapshot](remediation_worker/jobs/ebs_private_snapshot) | +| 37. | 5c8c26467a550e1fb6560c48 | RDS instance should restrict public access | [rds-remove-public-endpoint](remediation_worker/jobs/rds_remove_public_endpoint) | +| 38. | 5c8c264a7a550e1fb6560c4c | RDS should have automatic minor version upgrades enabled | [rds-enable-version-update](remediation_worker/jobs/rds_enable_version_update) | ## 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/ebs_private_snapshot/README.md b/remediation_worker/jobs/ebs_private_snapshot/README.md new file mode 100644 index 0000000..a2a39f0 --- /dev/null +++ b/remediation_worker/jobs/ebs_private_snapshot/README.md @@ -0,0 +1,75 @@ +# Configure the EBS volume snapshot as private. + +This job makes an EBS snapshot private by removing the keyword 'all' from GroupNames. + +## Getting Started + +##### Rule ID: +2cdb8877-7ac3-4483-9ed0-1e792171d125 + +##### Rule Name: +EBS volume snapshot should be private + +### 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 ebs_private_snapshot.py "`cat finding.json`" +``` + where finding.json has volume id and region info: + ```json + { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "snap-047ed496ef688a585", + "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/ebs_private_snapshot/__init__.py b/remediation_worker/jobs/ebs_private_snapshot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/ebs_private_snapshot/constraints.txt b/remediation_worker/jobs/ebs_private_snapshot/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/ebs_private_snapshot/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/ebs_private_snapshot/ebs_private_snapshot.py b/remediation_worker/jobs/ebs_private_snapshot/ebs_private_snapshot.py new file mode 100644 index 0000000..9ab4d21 --- /dev/null +++ b/remediation_worker/jobs/ebs_private_snapshot/ebs_private_snapshot.py @@ -0,0 +1,132 @@ +# 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. + +import json +import logging +import sys + +import boto3 +from botocore.exceptions import ClientError + +logging.basicConfig(level=logging.INFO) + + +class EBSPrivateSnapshot: + 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 + """ + logging.debug(payload) + remediation_entry = json.loads(payload) + notification_info = remediation_entry.get("notificationInfo", None) + finding_info = notification_info.get("FindingInfo", None) + + snapshot_id = finding_info.get("ObjectId", None) + region = finding_info.get("Region", None) + + if snapshot_id is None: + logging.error("Missing parameters for 'SNAPSHOT_ID'.") + raise Exception("Missing parameters for 'SNAPSHOT_ID'.") + + if region is None: + logging.error("Missing parameters for 'REGION'.") + raise Exception("Missing parameters for 'REGION'.") + + logging.debug("parsed params") + logging.debug(f" snapshot_id: {snapshot_id}") + logging.info(f" region: {region}") + + return { + "snapshot_id": snapshot_id, + "region": region + } + + def remediate(self, client, snapshot_id, region): + + """Set snapshots to private. + :param client: Instance of the AWS boto3 client. + :param snapshot_id: The id of the EBS snapshot. + :param region: The region of the EBS snapshot. + :type snapshot_id: str. + :returns: Bool signaling success or failure + :rtype: bool + """ + logging.info("Removing Public access by executing client.describe_snapshot_attribute") + logging.info("Attribute = createVolumePermission") + logging.info(f"SnapshotId={snapshot_id}") + + try: + # Get the permissions of the snapshot, no exeption expected + snapshot_permissions = client.describe_snapshot_attribute( + Attribute='createVolumePermission', + SnapshotId=snapshot_id + ).get('CreateVolumePermissions') + + logging.info(f"permission={snapshot_permissions}") + + # if createVolumePermission has "Group":"all", remove it + if snapshot_permissions: + for permission in snapshot_permissions: + if 'all' in permission['Group']: + logging.info(f"Found Public Snapshot: {snapshot_id}") + + # remove all from the groupname, no exception expected + client.modify_snapshot_attribute( + Attribute='createVolumePermission', + GroupNames=[ + 'all', + ], + OperationType='remove', + SnapshotId=snapshot_id, + ) + logging.info(f"Public access removed from {snapshot_id}") + return 0 + else: + logging.info(f"Snapshot {snapshot_id} is not public, exiting") + return 1 + + except ClientError as state_err: + error = state_err.response["Error"]["Code"] + logging.error(f"Got Exception={error}") + return 1 + except Exception as e: + error = "Receiving other exceptions {0}".format(str(e)) + logging.error(error) + return 1 + + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + client = boto3.client("ec2", region_name=params['region']) + + logging.debug( + "acquired ec2 client and parsed params - starting remediation." + ) + + return self.remediate(client=client, **params) + + +if __name__ == "__main__": + logging.info("ebs_private_snapshot.py called - running now") + obj = EBSPrivateSnapshot() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/ebs_private_snapshot/minimum_policy.json b/remediation_worker/jobs/ebs_private_snapshot/minimum_policy.json new file mode 100644 index 0000000..a033b19 --- /dev/null +++ b/remediation_worker/jobs/ebs_private_snapshot/minimum_policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ModifyPrivateSnapShot", + "Effect": "Allow", + "Action": [ + "ec2:DescribeSnapshotAttribute", + "ec2:ModifySnapshotAttribute" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/ebs_private_snapshot/requirements-dev.txt b/remediation_worker/jobs/ebs_private_snapshot/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ebs_private_snapshot/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/ebs_private_snapshot/requirements.txt b/remediation_worker/jobs/ebs_private_snapshot/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/ebs_private_snapshot/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/rds_enable_version_update/README.md b/remediation_worker/jobs/rds_enable_version_update/README.md new file mode 100644 index 0000000..a8e4066 --- /dev/null +++ b/remediation_worker/jobs/rds_enable_version_update/README.md @@ -0,0 +1,75 @@ +# Enable automatic minor version upgrade for RDS DBInstance + +This job enables automatic minor version upgrade RDS DBInstance. + +## Getting Started + +##### Rule ID: +5c8c264a7a550e1fb6560c4c + +##### Rule Name: +RDS should have automatic minor version upgrades enabled + +### 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 rds_enable_version_upgrade.py "`cat finding.json`" +``` + where finding.json has launch config name and region info: +```json + { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "rds-name", + "Region": "us-west-1" + } + } +} +``` + +## 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/rds_enable_version_update/__init__.py b/remediation_worker/jobs/rds_enable_version_update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/rds_enable_version_update/constraints.txt b/remediation_worker/jobs/rds_enable_version_update/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/rds_enable_version_update/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/rds_enable_version_update/minimum_policy.json b/remediation_worker/jobs/rds_enable_version_update/minimum_policy.json new file mode 100644 index 0000000..5569777 --- /dev/null +++ b/remediation_worker/jobs/rds_enable_version_update/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/rds_enable_version_update/rds_enable_version_update.py b/remediation_worker/jobs/rds_enable_version_update/rds_enable_version_update.py new file mode 100644 index 0000000..cb49a3e --- /dev/null +++ b/remediation_worker/jobs/rds_enable_version_update/rds_enable_version_update.py @@ -0,0 +1,121 @@ +# 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. + +import json +import logging +import sys + +import boto3 +from botocore.exceptions import ClientError + +logging.basicConfig(level=logging.INFO) + + +class RDSUpgradMinorVersion: + 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) + + database_id = finding_info.get("ObjectId", None) + region = finding_info.get("Region", None) + + if database_id is None: + logging.error("Missing parameters for 'DATABASE_ID'.") + raise Exception("Missing parameters for 'DATABASE_ID'.") + + logging.info("parsed params") + logging.info(" database_id: %s", database_id) + logging.info(" region: %s", region) + + return { + "database_id": database_id, + "region": region + } + + def remediate(self, client, database_id): + """Enable automatic minor version upgrades. Any instance with this value + disabled will be converted to true. + :param client: Instance of the AWS boto3 client. + :param database_id: The id of the RDS instanace. + :returns: updates flag for RDS auto upgrade minor version + """ + + logging.info("Updating auto minor version upgrade for %s", database_id) + + # convert the RDS auto minor version flag to True + try: + logging.info("Setting AutoMinorVersionUpgrade to True by calling client.modify_db_instance") + logging.info(f"DBInstanceIdentifier={database_id}") + client.modify_db_instance( + DBInstanceIdentifier=database_id, + AutoMinorVersionUpgrade=True, + ApplyImmediately=True) + + 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_instances" call + if state_err.response["Error"]["Code"] == "InvalidDBInstanceState": + logging.info( + "RDS database %s is in an unavailable state.", + database_id + ) + + logging.error( + "Remediation RDS database %s failed", + database_id + ) + return 1 + except Exception as e: + error = "Receiving other exceptions {0} while changing RDS database {1} minor version upgrade".format(str(e), instance_id) + logging.error(error) + return 1 + + logging.info("Updated auto minor version upgrade for %s successfully.", database_id) + 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 = 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['database_id']) + + +if __name__ == "__main__": + logging.info("rds_enable_version_update.py called - running now %s", + sys.argv[0]) + + obj = RDSUpgradMinorVersion() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/rds_enable_version_update/requirements-dev.txt b/remediation_worker/jobs/rds_enable_version_update/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/rds_enable_version_update/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/rds_enable_version_update/requirements.txt b/remediation_worker/jobs/rds_enable_version_update/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/rds_enable_version_update/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/rds_remove_public_endpoint/README.md b/remediation_worker/jobs/rds_remove_public_endpoint/README.md new file mode 100644 index 0000000..d2a271b --- /dev/null +++ b/remediation_worker/jobs/rds_remove_public_endpoint/README.md @@ -0,0 +1,74 @@ +# Disable public access to RDS DBInstances + +This job removes public access from RDS instances and makes the instances private. + +## Getting Started + +##### Rule ID: +5c8c26467a550e1fb6560c48 + +##### Rule Name: +RDS instance 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 rds_remove_public_endpoint.py "`cat finding.json`" +``` + where finding.json has volume 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/rds_remove_public_endpoint/__init__.py b/remediation_worker/jobs/rds_remove_public_endpoint/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/rds_remove_public_endpoint/constraints.txt b/remediation_worker/jobs/rds_remove_public_endpoint/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/rds_remove_public_endpoint/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/rds_remove_public_endpoint/minimum_policy.json b/remediation_worker/jobs/rds_remove_public_endpoint/minimum_policy.json new file mode 100644 index 0000000..5569777 --- /dev/null +++ b/remediation_worker/jobs/rds_remove_public_endpoint/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/rds_remove_public_endpoint/rds_remove_public_endpoint.py b/remediation_worker/jobs/rds_remove_public_endpoint/rds_remove_public_endpoint.py new file mode 100644 index 0000000..b56e143 --- /dev/null +++ b/remediation_worker/jobs/rds_remove_public_endpoint/rds_remove_public_endpoint.py @@ -0,0 +1,160 @@ +# 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 RDSRemovePublicEndpoint(): + 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) + + instance_id = finding_info.get("ObjectId", None) + region = finding_info.get("Region", None) + + if instance_id is None: + logging.error("Missing parameters for 'INSTANCE_ID'.") + raise Exception("Missing parameters for 'INSTANCE_ID'.") + + if region is None: + logging.error("Missing parameters for 'REGION'.") + raise Exception("Missing parameters for 'REGION'.") + + logging.info("parsed params") + logging.info(" instance_id: %s", instance_id) + logging.info(" region: %s", region) + + return { + "instance_id": instance_id, + "region": region + } + + def remediate(self, client, instance_id): + """ + Removes public access from RDS instances + and makes the instances private. + + Args: + instance_id ([str]): The ID of the RDS instance. + client: Instance of the AWS boto3 client. + """ + + logging.info("Beginning RDS public endpoint evaluation.") + + # Check to see if the database + # is publicly accessible + logging.info("executing client.describe_db_instances") + logging.info(f"RDS instance={instance_id}") + is_public = client.describe_db_instances( + DBInstanceIdentifier=instance_id).get( + "DBInstances")[0]["PubliclyAccessible"] + + # If it is public, set to private + if is_public: + logging.info( + "RDS database %s is public, setting to private...", + instance_id + ) + try: + logging.info("executing client.modify_db_instance and apply immediately") + logging.info("Attribute=PubliclyAccessible") + client.modify_db_instance( + DBInstanceIdentifier=instance_id, + PubliclyAccessible=False, + ApplyImmediately=True + ) + logging.info( + "RDS database %s is now private.", + instance_id + ) + 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_instances" call + if state_err.response["Error"]["Code"] == "InvalidDBInstanceState": + logging.info( + "RDS database %s is in an unavailable state. Waiting..", + instance_id + ) + + logging.error( + "Remediation RDS database %s failed", + instance_id + ) + return 1 + except Exception as e: + error = "Receiving other exceptions {0} while setting RDS database {1} to private".format(str(e), instance_id) + logging.error(error) + return 1 + + else: + logging.info( + "RDS database %s is private, taking no action.", + instance_id + ) + 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( + "rds_remove_public_endpoint.py called - running now %s", + sys.argv[0] + ) + obj = RDSRemovePublicEndpoint() + obj.run(sys.argv) diff --git a/remediation_worker/jobs/rds_remove_public_endpoint/requirements-dev.txt b/remediation_worker/jobs/rds_remove_public_endpoint/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/rds_remove_public_endpoint/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/rds_remove_public_endpoint/requirements.txt b/remediation_worker/jobs/rds_remove_public_endpoint/requirements.txt new file mode 100644 index 0000000..b239388 --- /dev/null +++ b/remediation_worker/jobs/rds_remove_public_endpoint/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_ebs_private_snapshot.py b/test/unit/test_ebs_private_snapshot.py new file mode 100644 index 0000000..15d3337 --- /dev/null +++ b/test/unit/test_ebs_private_snapshot.py @@ -0,0 +1,94 @@ +# 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.ebs_private_snapshot.ebs_private_snapshot import EBSPrivateSnapshot + +@pytest.fixture +def valid_payload(): + return """ { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } + } +""" + + +class TestEBSPrivateSnapshot: + def test_parse_payload(self, valid_payload): + obj = EBSPrivateSnapshot() + param = obj.parse(valid_payload) + assert "snapshot_id" in param + assert param["snapshot_id"] == "i-00347a2be30cf1a15" + assert "region" in param + assert param["region"] == "us-east-1" + + def test_remediate_success(self): + class TestClient(object): + def modify_snapshot_attribute(self, **kwargs): + return 0 + + def describe_snapshot_attribute(self, **kwargs): + return ({ + "SnapshotId": "snap-066877671789bd71b", + "CreateVolumePermissions": [ + { + "UserId": "123456789012", + 'Group': 'all' + } + + ] + }) + + client = TestClient() + obj = EBSPrivateSnapshot() + assert obj.remediate(client, "snap-01a972f6209ba8d24", "us-west-2") == 0 + + def test_remediate_not_success(self): + class TestClient(object): + def modify_snapshot_attribute(self, **kwargs): + return 0 + + def describe_snapshot_attribute(self, **kwargs): + return ({}) + + + client = TestClient() + obj = EBSPrivateSnapshot() + assert obj.remediate(client, "snap-01a972f6209ba8d24", "us-west-2") == 1 + + def test_remediate_not_success_exception(self): + class TestClient(object): + + def describe_snapshot_attribute(self, **kwargs): + return ({}) + + def modify_db_instance(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "InvalidSnapshot", + "Message": "NotFound", + } + }, + "InvalidSnapshot", + ) + + client = TestClient() + obj = EBSPrivateSnapshot() + assert obj.remediate(client, "snap-01a972f6209ba8d24", "us-west-2") == 1 diff --git a/test/unit/test_rds_enable_version_update.py b/test/unit/test_rds_enable_version_update.py new file mode 100644 index 0000000..01be971 --- /dev/null +++ b/test/unit/test_rds_enable_version_update.py @@ -0,0 +1,76 @@ +# 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.rds_enable_version_update.rds_enable_version_update import RDSUpgradMinorVersion + +@pytest.fixture +def valid_payload(): + return """ { + "notificationInfo": { + "FindingInfo": { + "ObjectId": "i-00347a2be30cf1a15", + "Region": "us-east-1" + } + } + } +""" + + +class TestRDSUpgradMinorVersion: + def test_parse_payload(self, valid_payload): + obj = RDSUpgradMinorVersion() + param = obj.parse(valid_payload) + assert "database_id" in param + assert param["database_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_instance(self, **kwargs): + return 1 + + def describe_db_instances(self, **kwargs): + rds = {'DBInstances':[{'PubliclyAccessible':True}]} + return rds + + client = TestClient() + obj = RDSUpgradMinorVersion() + assert obj.remediate(client, "database-1") == 0 + + def test_remediation_not_success(self, valid_payload): + class TestClient(object): + def modify_db_instance(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "InvalidDBInstanceState", + "Message": "InvalidDBInstanceState msg", + } + }, + "InvalidDBInstance", + ) + + def describe_db_instances(self, **kwargs): + rds = {'DBInstances':[{'PubliclyAccessible':True}]} + return rds + + + client = TestClient() + obj = RDSUpgradMinorVersion() + assert obj.remediate(client, "database-1") == 1 + diff --git a/test/unit/test_rds_remove_public_endpoint.py b/test/unit/test_rds_remove_public_endpoint.py new file mode 100644 index 0000000..83cccd8 --- /dev/null +++ b/test/unit/test_rds_remove_public_endpoint.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.rds_remove_public_endpoint.rds_remove_public_endpoint import RDSRemovePublicEndpoint + +@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 = RDSRemovePublicEndpoint() + 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_instance(self, **kwargs): + return 1 + + def describe_db_instances(self, **kwargs): + rds = {'DBInstances':[{'PubliclyAccessible':True}]} + return rds + + client = TestClient() + obj = RDSRemovePublicEndpoint() + assert obj.remediate(client, "database-1") == 0 + + def test_remediation_not_success(self, valid_payload): + class TestClient(object): + def modify_db_instance(self, **kwargs): + raise ClientError( + { + "Error": { + "Code": "InvalidDBInstanceState", + "Message": "InvalidDBInstanceState msg", + } + }, + "InvalidDBInstance", + ) + + def describe_db_instances(self, **kwargs): + rds = {'DBInstances':[{'PubliclyAccessible':True}]} + return rds + + + client = TestClient() + obj = RDSRemovePublicEndpoint() + assert obj.remediate(client, "database-1") == 1 + + \ No newline at end of file diff --git a/tox.ini b/tox.ini index 5b2ac50..7d3972a 100644 --- a/tox.ini +++ b/tox.ini @@ -52,7 +52,10 @@ envlist = unit-azure-postgresql-allow-access-to-azure-service-disabled unit-aws-s3-bucket-policy-allow-https unit-aws-sqs-queue-publicly-accessible - unit-kinesis-encrypt-stream + unit-ebs-private-snapshot + unit-rds-remove-public-endpoint + unit-rds_enable_version_update + unit-kinesis-encrypt-stream [testenv] @@ -307,12 +310,31 @@ changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_sqs_queue_publicly_accessible.py deps = -r remediation_worker/jobs/aws_sqs_queue_publicly_accessible/requirements-dev.txt +[testenv:unit-ebs-private-snapshot] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ebs_private_snapshot.py +deps = -r remediation_worker/jobs/ebs_private_snapshot/requirements-dev.txt + +[testenv:unit-rds-remove-public-endpoint] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_rds_remove_public_endpoint.py +deps = -r remediation_worker/jobs/rds_remove_public_endpoint/requirements-dev.txt + +[testenv:unit-rds_enable_version_update] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_rds_enable_version_update.py +deps = -r remediation_worker/jobs/rds_enable_version_update/requirements-dev.txt + [testenv:unit-kinesis-encrypt-stream] description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_kinesis_encrypt_stream.py deps = -r remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt + [testenv:unit-ec2-close-port-5601] description = Unit test the project changedir = test From 96cede7d07b91ccb5f8a6262e4bf049daef64e77 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Wed, 4 Aug 2021 23:17:00 +0530 Subject: [PATCH 10/23] PLA-29176 - Fix remediation jobs for port rules (#102) * PLA-29176 - Fix remediation jobs for port rules * PLA-29176 - updated requirements * PLA-29176 - Updated the public instance port remediation jobs * PLA-29176 - Fixed readme file * PLA-29176 - Fixed comments * PLA-29176 - Updated all the AWS port rule remediation jobs * PLA-29176 - Fixed requirements-dev file * PLA-29176 - Added comments --- .../README.md | 2 +- ...c2_administration_ports_ingress_allowed.py | 237 +++--------------- .../constraints.txt | 6 +- .../minimum_policy.json | 4 +- .../requirements.txt | 12 +- .../jobs/ec2_close_port_1433/README.md | 4 +- .../jobs/ec2_close_port_1433/constraints.txt | 6 +- .../ec2_close_port_1433.py | 103 ++++---- .../ec2_close_port_1433/minimum_policy.json | 3 +- .../jobs/ec2_close_port_1433/requirements.txt | 12 +- .../jobs/ec2_close_port_1521/README.md | 4 +- .../jobs/ec2_close_port_1521/constraints.txt | 6 +- .../ec2_close_port_1521.py | 103 ++++---- .../ec2_close_port_1521/minimum_policy.json | 3 +- .../jobs/ec2_close_port_1521/requirements.txt | 12 +- .../jobs/ec2_close_port_20/README.md | 4 +- .../jobs/ec2_close_port_20/constraints.txt | 6 +- .../ec2_close_port_20/ec2_close_port_20.py | 108 ++++---- .../ec2_close_port_20/minimum_policy.json | 3 +- .../jobs/ec2_close_port_20/requirements.txt | 12 +- .../jobs/ec2_close_port_21/README.md | 4 +- .../jobs/ec2_close_port_21/constraints.txt | 6 +- .../ec2_close_port_21/ec2_close_port_21.py | 103 ++++---- .../ec2_close_port_21/minimum_policy.json | 3 +- .../jobs/ec2_close_port_21/requirements.txt | 12 +- .../jobs/ec2_close_port_22/README.md | 4 +- .../jobs/ec2_close_port_22/constraints.txt | 6 +- .../ec2_close_port_22/ec2_close_port_22.py | 103 ++++---- .../ec2_close_port_22/minimum_policy.json | 3 +- .../jobs/ec2_close_port_22/requirements.txt | 12 +- .../jobs/ec2_close_port_23/README.md | 4 +- .../jobs/ec2_close_port_23/constraints.txt | 6 +- .../ec2_close_port_23/ec2_close_port_23.py | 103 ++++---- .../ec2_close_port_23/minimum_policy.json | 3 +- .../jobs/ec2_close_port_23/requirements.txt | 12 +- .../jobs/ec2_close_port_27017/README.md | 4 +- .../jobs/ec2_close_port_27017/constraints.txt | 6 +- .../ec2_close_port_27017.py | 103 ++++---- .../ec2_close_port_27017/minimum_policy.json | 3 +- .../ec2_close_port_27017/requirements.txt | 12 +- .../jobs/ec2_close_port_3306/README.md | 4 +- .../jobs/ec2_close_port_3306/constraints.txt | 6 +- .../ec2_close_port_3306.py | 103 ++++---- .../ec2_close_port_3306/minimum_policy.json | 3 +- .../jobs/ec2_close_port_3306/requirements.txt | 12 +- .../jobs/ec2_close_port_3389/README.md | 4 +- .../jobs/ec2_close_port_3389/constraints.txt | 12 +- .../ec2_close_port_3389.py | 103 ++++---- .../ec2_close_port_3389/minimum_policy.json | 5 +- .../jobs/ec2_close_port_3389/requirements.txt | 12 +- .../jobs/ec2_close_port_5439/README.md | 4 +- .../jobs/ec2_close_port_5439/constraints.txt | 6 +- .../ec2_close_port_5439.py | 103 ++++---- .../ec2_close_port_5439/minimum_policy.json | 3 +- .../jobs/ec2_close_port_5439/requirements.txt | 12 +- .../jobs/ec2_close_port_5601/README.md | 4 +- .../jobs/ec2_close_port_5601/constraints.txt | 12 +- .../ec2_close_port_5601.py | 103 ++++---- .../ec2_close_port_5601/minimum_policy.json | 3 +- .../ec2_close_port_5601/requirements-dev.txt | 2 +- .../jobs/ec2_close_port_5601/requirements.txt | 12 +- .../jobs/ec2_close_port_8080/README.md | 4 +- .../jobs/ec2_close_port_8080/constraints.txt | 6 +- .../ec2_close_port_8080.py | 103 ++++---- .../ec2_close_port_8080/minimum_policy.json | 3 +- .../jobs/ec2_close_port_8080/requirements.txt | 12 +- .../jobs/ec2_close_port_9200_9300/README.md | 4 +- .../ec2_close_port_9200_9300/constraints.txt | 6 +- .../ec2_close_port_9200_9300.py | 106 ++++---- .../minimum_policy.json | 3 +- .../ec2_close_port_9200_9300/requirements.txt | 12 +- .../requirements-dev.txt | 2 +- .../security_group_close_port_22/README.md | 4 +- .../constraints.txt | 6 +- .../minimum_policy.json | 3 +- .../requirements.txt | 12 +- .../security_group_close_port_22.py | 63 ++--- .../security_group_close_port_3389/README.md | 4 +- .../constraints.txt | 6 +- .../minimum_policy.json | 5 +- .../requirements.txt | 12 +- .../security_group_close_port_3389.py | 81 +++--- .../security_group_close_port_5432/README.md | 4 +- .../constraints.txt | 6 +- .../minimum_policy.json | 5 +- .../requirements.txt | 12 +- .../security_group_close_port_5432.py | 82 +++--- 87 files changed, 948 insertions(+), 1308 deletions(-) diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md index 9b512a8..c2b55e8 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/README.md @@ -14,7 +14,7 @@ Network ACL should restrict administration ports (3389 and 22) from public acces ### Prerequisites -The provided AWS credential must have access to `ec2:CreateNetworkAclEntry`, `ec2:DeleteNetworkAclEntry`, `DescribeNetworkAcls` and `ec2:ReplaceNetworkAclEntry`. +The provided AWS credential must have access to `ec2:DeleteNetworkAclEntry`, `ec2:DescribeNetworkAcls`. You may find the latest example policy file [here](minimum_policy.json) diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py index d8c32f4..1659c68 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py @@ -61,201 +61,6 @@ def parse(self, payload): logging.info(return_dict) return return_dict - def create_list_of_rule_nos(self, network_acl_entries): - """Creates List of Rule Numbers in the Network Acl - :param network_acl_entries: List of Network Acl entries. - :type network_acl_id: str. - :returns: List of Rule Numbers. - :rtype: list. - """ - rule_nos = [] - for entry in network_acl_entries["Entries"]: - rule_nos.append(entry["RuleNumber"]) - return rule_nos - - def create_list_of_port_range(self, network_acl_entries): - """Creates List of Port Ranges in the Network Acl - :param network_acl_entries: List of Network Acl entries. - :type network_acl_id: str. - :returns: List of Port Ranges. - :rtype: list. - """ - port_ranges = [] - for entry in network_acl_entries["Entries"]: - if "PortRange" not in entry: - continue - else: - port = (entry["PortRange"]["From"], entry["PortRange"]["To"]) - port_ranges.append(port) - return port_ranges - - def check_if_nacl_exists( - self, network_acl_entries, port_from, port_to, port_ranges - ): - """Checks if the Network ACL Entry already exists - :param network_acl_entries: List of Network Acl entries. - :param port_from: Port Range from. - :param port_to: Port Range To. - :param port_ranges: List of port ranges. - :type network_acl_entries: list - :type port_from: int - :type port_to: int - :type port_ranges: list - :returns: Boolean value indicating if the entry with given port range already exists - :rtype: bool - """ - for nacl_entry in network_acl_entries["Entries"]: - if "PortRange" not in nacl_entry: - continue - elif ( - nacl_entry["PortRange"]["From"] == port_from - and nacl_entry["PortRange"]["To"] == port_to - ): - return True - else: - continue - for port in port_ranges: - if port[0] == port_from and port[1] == port_to: - return True - return False - - def find_and_remove_port( - self, - network_acl_id, - client, - network_acl_entries, - port_no, - rule_nos, - port_ranges, - ): - """Find and remove port 22 and 3389 from Network Acl Entries - :param network_acl_id: Network Acl Id. - :param client: Instance of the AWS boto3 client. - :param network_acl_entries: List of Network Acl Entries. - :param port_no: Port No. to remove. - :param rule_nos: List of Rule Numbers. - :type rule_nos: list. - :type port_no: int. - :type network_acl_entries: list. - :type network_acl_id: str. - :type client: object. - :returns: None. - :rtype: None. - """ - for entry in network_acl_entries["Entries"]: - if ( - entry["Egress"] is False - and entry["RuleAction"] == "allow" - and entry["Protocol"] in ["6", "-1"] - and entry["CidrBlock"] == "0.0.0.0/0" - ): - if "PortRange" not in entry or entry["PortRange"] == { - "From": port_no, - "To": port_no, - }: - client.delete_network_acl_entry( - Egress=False, - NetworkAclId=network_acl_id, - RuleNumber=entry["RuleNumber"], - ) - elif ( - entry["PortRange"]["From"] < port_no - and entry["PortRange"]["To"] == port_no - ): - portrange_to = port_no - 1 - - if self.check_if_nacl_exists( - network_acl_entries, - entry["PortRange"]["From"], - portrange_to, - port_ranges, - ): - client.delete_network_acl_entry( - Egress=False, - NetworkAclId=network_acl_id, - RuleNumber=entry["RuleNumber"], - ) - else: - client.replace_network_acl_entry( - CidrBlock=entry["CidrBlock"], - Egress=entry["Egress"], - NetworkAclId=network_acl_id, - PortRange={ - "From": entry["PortRange"]["From"], - "To": portrange_to, - }, - Protocol=entry["Protocol"], - RuleAction=entry["RuleAction"], - RuleNumber=entry["RuleNumber"], - ) - - port = (entry["PortRange"]["From"], portrange_to) - port_ranges.append(port) - elif ( - entry["PortRange"]["From"] < port_no - and entry["PortRange"]["To"] > port_no - ): - rule_no = entry["RuleNumber"] + 10 - - while rule_no in rule_nos: - rule_no = rule_no + 10 - - portrange_to = port_no - 1 - - if self.check_if_nacl_exists( - network_acl_entries, - entry["PortRange"]["From"], - portrange_to, - port_ranges, - ): - client.delete_network_acl_entry( - Egress=False, - NetworkAclId=network_acl_id, - RuleNumber=entry["RuleNumber"], - ) - else: - client.replace_network_acl_entry( - CidrBlock=entry["CidrBlock"], - Egress=entry["Egress"], - NetworkAclId=network_acl_id, - PortRange={ - "From": entry["PortRange"]["From"], - "To": portrange_to, - }, - Protocol=entry["Protocol"], - RuleAction=entry["RuleAction"], - RuleNumber=entry["RuleNumber"], - ) - - port = (entry["PortRange"]["From"], portrange_to) - port_ranges.append(port) - - portrange_from = port_no + 1 - - if self.check_if_nacl_exists( - network_acl_entries, - portrange_from, - entry["PortRange"]["To"], - port_ranges, - ): - continue - else: - client.create_network_acl_entry( - CidrBlock=entry["CidrBlock"], - Egress=entry["Egress"], - NetworkAclId=network_acl_id, - PortRange={ - "From": portrange_from, - "To": entry["PortRange"]["To"], - }, - Protocol=entry["Protocol"], - RuleAction=entry["RuleAction"], - RuleNumber=rule_no, - ) - rule_nos.append(rule_no) - port = (portrange_from, entry["PortRange"]["To"]) - port_ranges.append(port) - def remediate(self, region, client, network_acl_id, cloud_account_id): """Remove Network ACL Rules that allows public access to administration ports (3389 and 22) :param region: The buckets region @@ -276,23 +81,39 @@ def remediate(self, region, client, network_acl_id, cloud_account_id): logging.info( "executing client.describe_network_acls to get network acl" ) + logging.info(" executing client.describe_network_acls") + logging.info(f" NetworkAclId: {network_acl_id}") + # List network acl details network_acl = client.describe_network_acls( NetworkAclIds=[network_acl_id] ) network_acl_entries = network_acl["NetworkAcls"][0] - #Create List of Rule Numbers - rule_nos = self.create_list_of_rule_nos(network_acl_entries) - #Create List of Port Ranges - port_ranges = self.create_list_of_port_range(network_acl_entries) - #Remove the port from the Network ACL entries - self.find_and_remove_port( - network_acl_id, - client, - network_acl_entries, - port_no, - rule_nos, - port_ranges, - ) + for entry in network_acl_entries["Entries"]: + #Searching for the ingress nacl entries with RuleAction = allow, + #protocol as tcp or all traffic, CidrBlock="0.0.0.0/0" and the + #port range inclusive of port 22 and 3389 + if ( + entry["Egress"] is False + and entry["RuleAction"] == "allow" + and entry["Protocol"] in ["6", "-1"] + and entry["CidrBlock"] == "0.0.0.0/0" + and ( + "PortRange" not in entry + or ( + entry["PortRange"]["From"] <= port_no + and entry["PortRange"]["To"] >= port_no + ) + ) + ): + # Delete nacl entry which provides public access to administration ports (3389 and 22) + logging.info(" executing client.delete_network_acl_entry") + logging.info(f" NetworkAclId: {network_acl_id}") + logging.info(f" RuleNumber: {entry['RuleNumber']}") + client.delete_network_acl_entry( + Egress=False, + NetworkAclId=network_acl_id, + RuleNumber=entry["RuleNumber"], + ) logging.info("successfully completed remediation job") except Exception as e: logging.error(f"{str(e)}") diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/constraints.txt b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/constraints.txt index 68a9723..98a054e 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/constraints.txt +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/constraints.txt @@ -29,9 +29,9 @@ py==1.9.0 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -s3transfer==0.3.4 \ - --hash=sha256:1e28620e5b444652ed752cf87c7e0cb15b0e578972568c6609f0f18212f259ed \ - --hash=sha256:7fdddb4f22275cf1d32129e21f056337fd2a80b6ccef1664528145b72c49e6d2 +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json index b3a9b8d..142b9b9 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/minimum_policy.json @@ -5,10 +5,8 @@ "Sid": "RemoveAdministrationPortsIngress", "Effect": "Allow", "Action": [ - "ec2:CreateNetworkAclEntry", "ec2:DescribeNetworkAcls", - "ec2:DeleteNetworkAclEntry", - "ec2:ReplaceNetworkAclEntry" + "ec2:DeleteNetworkAclEntry" ], "Resource": "*" } diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/requirements.txt b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/requirements.txt index ae343fd..93f7845 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/requirements.txt +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.16.60 \ - --hash=sha256:10e8d9b18a8ae15677e850c7240140b9539635a03098f01dfdd75b2042d15862 \ - --hash=sha256:aee742f2a2315244fb31a507f65d8809fcd0029508c0b12be8611ddd2075b666 -botocore==1.19.60 \ - --hash=sha256:423a1a9502bd7bc5db8c6e64f9374f64d8ac18e6b870278a9ff65f59d268cd58 \ - --hash=sha256:80dd615a34c7e2c73606070a9358f7b5c1cb0c9989348306c1c9ddff45bb6ebe +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/ec2_close_port_1433/README.md b/remediation_worker/jobs/ec2_close_port_1433/README.md index 5cd1cec..5154e09 100644 --- a/remediation_worker/jobs/ec2_close_port_1433/README.md +++ b/remediation_worker/jobs/ec2_close_port_1433/README.md @@ -1,6 +1,6 @@ # Close Port 1433 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 1433 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 1433 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 1433 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ EC2 instance should restrict public access to SQL Server port (1433) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_1433/constraints.txt b/remediation_worker/jobs/ec2_close_port_1433/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_1433/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_1433/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py b/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py index 10fb11c..189efe1 100644 --- a/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py +++ b/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 1433 of all security groups attached to an EC2 instance. - + """Block public access to port 1433 of all security groups attached to an EC2 instance + by removing all the rules with port 1433 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 1433 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 1433 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 1433 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): diff --git a/remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_1433/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_1433/requirements.txt b/remediation_worker/jobs/ec2_close_port_1433/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_1433/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_1433/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_1521/README.md b/remediation_worker/jobs/ec2_close_port_1521/README.md index b1a8f9a..5133eca 100644 --- a/remediation_worker/jobs/ec2_close_port_1521/README.md +++ b/remediation_worker/jobs/ec2_close_port_1521/README.md @@ -1,6 +1,6 @@ # Close Port 1521 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 1521 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 1521 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 1521 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -15,7 +15,7 @@ EC2 instance should restrict public access to Oracle SQL port (1521) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_1521/constraints.txt b/remediation_worker/jobs/ec2_close_port_1521/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_1521/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_1521/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py b/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py index 541f9ca..e7f3832 100644 --- a/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py +++ b/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 1521 of all security groups attached to an EC2 instance. - + """Block public access to port 1521 of all security groups attached to an EC2 instance + by removing all the rules with port 1521 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 1521 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 1521 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 1521 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): diff --git a/remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_1521/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_1521/requirements.txt b/remediation_worker/jobs/ec2_close_port_1521/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_1521/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_1521/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_20/README.md b/remediation_worker/jobs/ec2_close_port_20/README.md index 6fa507c..c6106fc 100644 --- a/remediation_worker/jobs/ec2_close_port_20/README.md +++ b/remediation_worker/jobs/ec2_close_port_20/README.md @@ -1,6 +1,6 @@ # Close Port 20 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 20 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 20 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 20 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ EC2 instance should restrict public access to FTP data port (20) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_20/constraints.txt b/remediation_worker/jobs/ec2_close_port_20/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_20/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_20/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py b/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py index 5180205..9f0bd1d 100644 --- a/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py +++ b/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -24,10 +23,9 @@ class EC2ClosePort20(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 @@ -58,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 20 of all security groups attached to an EC2 instance. - + """Block public access to port 20 of all security groups attached to an EC2 instance + by removing all the rules with port 20 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 @@ -67,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 20 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 20 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 20 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): @@ -139,8 +125,6 @@ def run(self, args): return rc - - if __name__ == "__main__": logging.info(f"{sys.argv[0]} called - running now") obj = EC2ClosePort20() diff --git a/remediation_worker/jobs/ec2_close_port_20/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_20/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_20/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_20/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_20/requirements.txt b/remediation_worker/jobs/ec2_close_port_20/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_20/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_20/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_21/README.md b/remediation_worker/jobs/ec2_close_port_21/README.md index 5a9e9f4..1f577ab 100644 --- a/remediation_worker/jobs/ec2_close_port_21/README.md +++ b/remediation_worker/jobs/ec2_close_port_21/README.md @@ -1,6 +1,6 @@ # Close Port 21 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 21 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 21 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 21 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -15,7 +15,7 @@ EC2 instance should restrict public access to FTP control port (21) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_21/constraints.txt b/remediation_worker/jobs/ec2_close_port_21/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_21/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_21/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py b/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py index a6fdb13..53c87f4 100644 --- a/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py +++ b/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 21 of all security groups attached to an EC2 instance. - + """Block public access to port 21 of all security groups attached to an EC2 instance + by removing all the rules with port 21 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 21 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 21 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 21 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): diff --git a/remediation_worker/jobs/ec2_close_port_21/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_21/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_21/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_21/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_21/requirements.txt b/remediation_worker/jobs/ec2_close_port_21/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_21/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_21/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_22/README.md b/remediation_worker/jobs/ec2_close_port_22/README.md index 54c0278..a102713 100644 --- a/remediation_worker/jobs/ec2_close_port_22/README.md +++ b/remediation_worker/jobs/ec2_close_port_22/README.md @@ -1,6 +1,6 @@ # Close Port 22 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 22 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 22 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 22 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ An EC2 instance's SSH port (22) is accessible from the public Internet for any s ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_22/constraints.txt b/remediation_worker/jobs/ec2_close_port_22/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_22/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_22/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py b/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py index 608f7a8..51ee27a 100644 --- a/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py +++ b/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 22 of all security groups attached to an EC2 instance. - + """Block public access to port 22 of all security groups attached to an EC2 instance + by removing all the rules with port 22 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 22 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 22 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 22 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): diff --git a/remediation_worker/jobs/ec2_close_port_22/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_22/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_22/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_22/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_22/requirements.txt b/remediation_worker/jobs/ec2_close_port_22/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_22/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_22/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_23/README.md b/remediation_worker/jobs/ec2_close_port_23/README.md index 29668a7..5762a01 100644 --- a/remediation_worker/jobs/ec2_close_port_23/README.md +++ b/remediation_worker/jobs/ec2_close_port_23/README.md @@ -1,6 +1,6 @@ # Close Port 23 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 23 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 23 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 23 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ EC2 instance should restrict public access to Telnet port (23) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_23/constraints.txt b/remediation_worker/jobs/ec2_close_port_23/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_23/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_23/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py b/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py index 37c9069..69a28bc 100644 --- a/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py +++ b/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 23 of all security groups attached to an EC2 instance. - + """Block public access to port 23 of all security groups attached to an EC2 instance + by removing all the rules with port 23 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 23 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 23 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 23 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): diff --git a/remediation_worker/jobs/ec2_close_port_23/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_23/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_23/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_23/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_23/requirements.txt b/remediation_worker/jobs/ec2_close_port_23/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_23/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_23/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_27017/README.md b/remediation_worker/jobs/ec2_close_port_27017/README.md index 2439b63..f413f3b 100644 --- a/remediation_worker/jobs/ec2_close_port_27017/README.md +++ b/remediation_worker/jobs/ec2_close_port_27017/README.md @@ -1,6 +1,6 @@ # Close Port 27017 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 27017 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 27017 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 27017 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -15,7 +15,7 @@ EC2 instance should restrict public access to MongoDB server port (27017) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_27017/constraints.txt b/remediation_worker/jobs/ec2_close_port_27017/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_27017/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_27017/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py b/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py index 24c8744..85f1bea 100644 --- a/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py +++ b/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 27017 of all security groups attached to an EC2 instance. - + """Block public access to port 27017 of all security groups attached to an EC2 instance + by removing all the rules with port 27017 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 27017 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 27017 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 27017 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): diff --git a/remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_27017/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_27017/requirements.txt b/remediation_worker/jobs/ec2_close_port_27017/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_27017/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_27017/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_3306/README.md b/remediation_worker/jobs/ec2_close_port_3306/README.md index 27e409b..ef6615f 100644 --- a/remediation_worker/jobs/ec2_close_port_3306/README.md +++ b/remediation_worker/jobs/ec2_close_port_3306/README.md @@ -1,6 +1,6 @@ # Close Port 3306 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 3306 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 3306 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 3306 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -15,7 +15,7 @@ EC2 instance should restrict public access to MySQL server port (3306) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_3306/constraints.txt b/remediation_worker/jobs/ec2_close_port_3306/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_3306/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_3306/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py b/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py index fa81036..a7f72bf 100644 --- a/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py +++ b/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 3306 of all security groups attached to an EC2 instance. - + """Block public access to port 3306 of all security groups attached to an EC2 instance + by removing all the rules with port 3306 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 3306 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 3306 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 3306 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): diff --git a/remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_3306/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_3306/requirements.txt b/remediation_worker/jobs/ec2_close_port_3306/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_3306/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_3306/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_3389/README.md b/remediation_worker/jobs/ec2_close_port_3389/README.md index 6245d6f..0f07d7a 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/README.md +++ b/remediation_worker/jobs/ec2_close_port_3389/README.md @@ -1,6 +1,6 @@ # Close Port 3389 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 3389 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 3389 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 3389 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ An EC2 instance's Remote Desktop port (3389) is accessible from the public Inter ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_3389/constraints.txt b/remediation_worker/jobs/ec2_close_port_3389/constraints.txt index 6b211d2..1c0a19d 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_3389/constraints.txt @@ -2,15 +2,15 @@ docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 -jmespath==0.10.0 \ - --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ - --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +jmespath==0.7.1 \ + --hash=sha256:cb99b58dcf853f791bec28aa016c121663b79ee6125c892297d773d7fd4dcecf \ + --hash=sha256:cd5a12ee3dfa470283a020a35e69e83b0700d44fe413014fd35ad5584c5f5fd1 python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py b/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py index 4510429..1e4344b 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py +++ b/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging @@ -58,8 +57,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 3389 of all security groups attached to an EC2 instance. - + """Block public access to port 3389 of all security groups attached to an EC2 instance + by removing all the rules with port 3389 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 @@ -67,62 +66,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 3389 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 3389 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 3389 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): diff --git a/remediation_worker/jobs/ec2_close_port_3389/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_3389/minimum_policy.json index ba2ba9a..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_3389/minimum_policy.json @@ -2,11 +2,12 @@ "Version": "2012-10-17", "Statement": [ { - "Sid": "EC2ClosePort3389", + "Sid": "EC2ClosePort22", "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_3389/requirements.txt b/remediation_worker/jobs/ec2_close_port_3389/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_3389/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_5439/README.md b/remediation_worker/jobs/ec2_close_port_5439/README.md index 807aeba..c7faec7 100644 --- a/remediation_worker/jobs/ec2_close_port_5439/README.md +++ b/remediation_worker/jobs/ec2_close_port_5439/README.md @@ -1,6 +1,6 @@ # Close Port 5439 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 5439 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 5439 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 5439 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ EC2 instance should restrict public access to Redshift port (5439) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_5439/constraints.txt b/remediation_worker/jobs/ec2_close_port_5439/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_5439/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_5439/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py b/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py index 716c29c..77802c5 100644 --- a/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py +++ b/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 5439 of all security groups attached to an EC2 instance. - + """Block public access to port 5439 of all security groups attached to an EC2 instance + by removing all the rules with port 5439 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 5439 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 5439 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 5439 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): diff --git a/remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_5439/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_5439/requirements.txt b/remediation_worker/jobs/ec2_close_port_5439/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_5439/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_5439/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_5601/README.md b/remediation_worker/jobs/ec2_close_port_5601/README.md index d0bd89b..ce8c0e2 100644 --- a/remediation_worker/jobs/ec2_close_port_5601/README.md +++ b/remediation_worker/jobs/ec2_close_port_5601/README.md @@ -1,6 +1,6 @@ # Close Port 5601 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 5601 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 5601 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 5601 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ EC2 instance should restrict public access to Kibana port (5601) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_5601/constraints.txt b/remediation_worker/jobs/ec2_close_port_5601/constraints.txt index 6b211d2..1c0a19d 100644 --- a/remediation_worker/jobs/ec2_close_port_5601/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_5601/constraints.txt @@ -2,15 +2,15 @@ docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 -jmespath==0.10.0 \ - --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ - --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +jmespath==0.7.1 \ + --hash=sha256:cb99b58dcf853f791bec28aa016c121663b79ee6125c892297d773d7fd4dcecf \ + --hash=sha256:cd5a12ee3dfa470283a020a35e69e83b0700d44fe413014fd35ad5584c5f5fd1 python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py b/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py index d8f1389..2b19fbc 100644 --- a/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py +++ b/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 5601 of all security groups attached to an EC2 instance. - + """Block public access to port 5601 of all security groups attached to an EC2 instance + by removing all the rules with port 5601 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 5601 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 5601 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 5601 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): diff --git a/remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_5601/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_5601/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_5601/requirements-dev.txt index 9412e93..c9dd3c9 100644 --- a/remediation_worker/jobs/ec2_close_port_5601/requirements-dev.txt +++ b/remediation_worker/jobs/ec2_close_port_5601/requirements-dev.txt @@ -3,4 +3,4 @@ pytest==6.0.1 \ --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ - --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad \ No newline at end of file diff --git a/remediation_worker/jobs/ec2_close_port_5601/requirements.txt b/remediation_worker/jobs/ec2_close_port_5601/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_5601/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_5601/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_8080/README.md b/remediation_worker/jobs/ec2_close_port_8080/README.md index 280bdf9..67d5265 100644 --- a/remediation_worker/jobs/ec2_close_port_8080/README.md +++ b/remediation_worker/jobs/ec2_close_port_8080/README.md @@ -1,6 +1,6 @@ # Close Port 8080 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 22 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 8080 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 8080 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ EC2 instance should restrict public access to TCP port (8080) ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_8080/constraints.txt b/remediation_worker/jobs/ec2_close_port_8080/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_8080/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_8080/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py b/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py index 69f16c4..75865c5 100644 --- a/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py +++ b/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 8080 of all security groups attached to an EC2 instance. - + """Block public access to port 8080 of all security groups attached to an EC2 instance + by removing all the rules with port 8080 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 @@ -66,62 +65,50 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - port = 8080 - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + try: + port = 8080 + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 8080 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): diff --git a/remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_8080/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_8080/requirements.txt b/remediation_worker/jobs/ec2_close_port_8080/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_8080/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_8080/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/ec2_close_port_9200_9300/README.md b/remediation_worker/jobs/ec2_close_port_9200_9300/README.md index 2533c24..a322715 100644 --- a/remediation_worker/jobs/ec2_close_port_9200_9300/README.md +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/README.md @@ -1,6 +1,6 @@ # Close Port 9200, 9300 for all Security Groups associated with an EC2 Instance -This job blocks public access to port 9200, 9300 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +This job blocks public access to port 9200, 9300 for both IPv4 and IPv6 for all security groups associated with an EC2 instance by removing all the ingress security group rules containing port 9200, 9300 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ EC2 instance should restrict public access to Elasticsearch ports (9200 and 9300 ### Prerequisites -The provided AWS credential must have access to `ec2:DescribeInstances` and `ec2:RevokeSecurityGroupIngress`. +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) diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/constraints.txt b/remediation_worker/jobs/ec2_close_port_9200_9300/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/ec2_close_port_9200_9300/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py b/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py index 1369ae2..b3979c1 100644 --- a/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging import sys @@ -57,8 +56,8 @@ def parse(self, payload): return {"instance_id": instance_id}, region def remediate(self, client, instance_id): - """Block public access to port 9200_9300 of all security groups attached to an EC2 instance. - + """Block public access to port 9200 and 9300 of all security groups attached to an EC2 instance + by removing all the rules with port 9200 and 9300 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 @@ -66,62 +65,51 @@ def remediate(self, client, instance_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - ports = [9200, 9300] - - for port in ports: - 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"] - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info(f"successfully executed remediation for port: {port}") + try: + ports = [9200, 9300] + 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"] + for port in ports: + 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"] == "tcp" + 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 27017 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 diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json b/remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json index 3140049..e91af08 100644 --- a/remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/minimum_policy.json @@ -6,7 +6,8 @@ "Effect": "Allow", "Action": [ "ec2:DescribeInstances", - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/requirements.txt b/remediation_worker/jobs/ec2_close_port_9200_9300/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/ec2_close_port_9200_9300/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/kinesis_encrypt_stream/requirements-dev.txt b/remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt index 9412e93..c9dd3c9 100644 --- a/remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt +++ b/remediation_worker/jobs/kinesis_encrypt_stream/requirements-dev.txt @@ -3,4 +3,4 @@ pytest==6.0.1 \ --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ - --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad \ No newline at end of file diff --git a/remediation_worker/jobs/security_group_close_port_22/README.md b/remediation_worker/jobs/security_group_close_port_22/README.md index 112feaa..b6f2a54 100644 --- a/remediation_worker/jobs/security_group_close_port_22/README.md +++ b/remediation_worker/jobs/security_group_close_port_22/README.md @@ -1,6 +1,6 @@ # Close Port 22 for a Security Group -This job blocks public access to port 22 for both IPv4 and IPv6. +This job blocks public access to port 22 for both IPv4 and IPv6 by removing all the ingress security group rules containing port 22 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ A security group's SSH port (22) is accessible through any source address ### Prerequisites -The provided AWS credential must have access to `ec2:RevokeSecurityGroupIngress`. +The provided AWS credential must have access to `ec2:RevokeSecurityGroupIngress`, `ec2:DescribeSecurityGroupRules`. You may find the latest example policy file [here](minimum_policy.json) diff --git a/remediation_worker/jobs/security_group_close_port_22/constraints.txt b/remediation_worker/jobs/security_group_close_port_22/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/security_group_close_port_22/constraints.txt +++ b/remediation_worker/jobs/security_group_close_port_22/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/security_group_close_port_22/minimum_policy.json b/remediation_worker/jobs/security_group_close_port_22/minimum_policy.json index 960ac21..c6ceba1 100644 --- a/remediation_worker/jobs/security_group_close_port_22/minimum_policy.json +++ b/remediation_worker/jobs/security_group_close_port_22/minimum_policy.json @@ -5,7 +5,8 @@ "Sid": "SecurityGroupClosePort22", "Effect": "Allow", "Action": [ - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/security_group_close_port_22/requirements.txt b/remediation_worker/jobs/security_group_close_port_22/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/security_group_close_port_22/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_22/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/security_group_close_port_22/security_group_close_port_22.py b/remediation_worker/jobs/security_group_close_port_22/security_group_close_port_22.py index 7c42b60..91a4b7d 100644 --- a/remediation_worker/jobs/security_group_close_port_22/security_group_close_port_22.py +++ b/remediation_worker/jobs/security_group_close_port_22/security_group_close_port_22.py @@ -49,7 +49,8 @@ def parse(self, payload): } def remediate(self, client, security_group_id): - """Block public access to port 22 for both IPv4 and IPv6. + """Block public access to port 22 for both IPv4 and IPv6 by + removing all the rules containing port 22 in the range. :param client: Instance of the AWS boto3 client. :param security_group_id: The ID of the security group. You must specify either the security group ID or the @@ -62,33 +63,39 @@ def remediate(self, client, security_group_id): """ port = 22 - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") - logcall( - client.revoke_security_group_ingress, - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - logcall( - client.revoke_security_group_ingress, - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], - ) - + try: + 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"] == "tcp" + 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 22 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): diff --git a/remediation_worker/jobs/security_group_close_port_3389/README.md b/remediation_worker/jobs/security_group_close_port_3389/README.md index b431123..dcf3ab7 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/README.md +++ b/remediation_worker/jobs/security_group_close_port_3389/README.md @@ -1,6 +1,6 @@ # Close Port 3389 for a Security Group -This job blocks public access to port 3389 for both IPv4 and IPv6. +This job blocks public access to port 3389 for both IPv4 and IPv6 by removing all the ingress security group rules containing port 3389 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ A security group's Remote Desktop port (3389) is accessible through any source a ### Prerequisites -The provided AWS credential must have access to `ec2:RevokeSecurityGroupIngress`. +The provided AWS credential must have access to `ec2:RevokeSecurityGroupIngress`, `ec2:DescribeSecurityGroupRules`. You may find the latest example policy file [here](minimum_policy.json) diff --git a/remediation_worker/jobs/security_group_close_port_3389/constraints.txt b/remediation_worker/jobs/security_group_close_port_3389/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/constraints.txt +++ b/remediation_worker/jobs/security_group_close_port_3389/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/security_group_close_port_3389/minimum_policy.json b/remediation_worker/jobs/security_group_close_port_3389/minimum_policy.json index 5e8d6d9..c6ceba1 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/minimum_policy.json +++ b/remediation_worker/jobs/security_group_close_port_3389/minimum_policy.json @@ -2,10 +2,11 @@ "Version": "2012-10-17", "Statement": [ { - "Sid": "EC2ClosePort22And3389", + "Sid": "SecurityGroupClosePort22", "Effect": "Allow", "Action": [ - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/security_group_close_port_3389/requirements.txt b/remediation_worker/jobs/security_group_close_port_3389/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_3389/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/security_group_close_port_3389/security_group_close_port_3389.py b/remediation_worker/jobs/security_group_close_port_3389/security_group_close_port_3389.py index fddf675..1a27438 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/security_group_close_port_3389.py +++ b/remediation_worker/jobs/security_group_close_port_3389/security_group_close_port_3389.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging @@ -58,7 +57,8 @@ def parse(self, payload): return {"security_group_id": security_group_id}, region def remediate(self, client, security_group_id): - """Block public access to port 3389 for both IPv4 and IPv6. + """Block public access to port 3389 for both IPv4 and IPv6 by + removing all the rules containing port 3389 in the range. :param client: Instance of the AWS boto3 client. :param security_group_id: The ID of the security group. You must specify either the security group ID or the @@ -70,55 +70,40 @@ def remediate(self, client, security_group_id): :raises: botocore.exceptions.ClientError """ - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") port = 3389 try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 3389 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): diff --git a/remediation_worker/jobs/security_group_close_port_5432/README.md b/remediation_worker/jobs/security_group_close_port_5432/README.md index f4682b9..ca3afb7 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/README.md +++ b/remediation_worker/jobs/security_group_close_port_5432/README.md @@ -1,6 +1,6 @@ # Close Port 5432 for a Security Group -This job blocks public access to port 5432 for both IPv4 and IPv6. +This job blocks public access to port 5432 for both IPv4 and IPv6 by removing all the ingress security group rules containing port 5432 in the port range and source as "0.0.0.0/0" or "::/0". ### Applicable Rule @@ -14,7 +14,7 @@ A security group's PostgreSQL Server port (5432) is accessible through any sourc ### Prerequisites -The provided AWS credential must have access to `ec2:RevokeSecurityGroupIngress`. +The provided AWS credential must have access to `ec2:RevokeSecurityGroupIngress`, `ec2:DescribeSecurityGroupRules`. You may find the latest example policy file [here](minimum_policy.json) diff --git a/remediation_worker/jobs/security_group_close_port_5432/constraints.txt b/remediation_worker/jobs/security_group_close_port_5432/constraints.txt index 6b211d2..0d9b3c2 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/constraints.txt +++ b/remediation_worker/jobs/security_group_close_port_5432/constraints.txt @@ -8,9 +8,9 @@ jmespath==0.10.0 \ python-dateutil==2.8.1 \ --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a -s3transfer==0.3.3 \ - --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ - --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +s3transfer==0.5.0 \ + --hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \ + --hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 urllib3==1.25.9 \ --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/security_group_close_port_5432/minimum_policy.json b/remediation_worker/jobs/security_group_close_port_5432/minimum_policy.json index 660300e..c6ceba1 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/minimum_policy.json +++ b/remediation_worker/jobs/security_group_close_port_5432/minimum_policy.json @@ -2,10 +2,11 @@ "Version": "2012-10-17", "Statement": [ { - "Sid": "SecurityGroupClosePort5432", + "Sid": "SecurityGroupClosePort22", "Effect": "Allow", "Action": [ - "ec2:RevokeSecurityGroupIngress" + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroupRules" ], "Resource": "*" } diff --git a/remediation_worker/jobs/security_group_close_port_5432/requirements.txt b/remediation_worker/jobs/security_group_close_port_5432/requirements.txt index b239388..93f7845 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_5432/requirements.txt @@ -1,6 +1,6 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 +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/security_group_close_port_5432/security_group_close_port_5432.py b/remediation_worker/jobs/security_group_close_port_5432/security_group_close_port_5432.py index 4a2b11e..acdbfcc 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/security_group_close_port_5432.py +++ b/remediation_worker/jobs/security_group_close_port_5432/security_group_close_port_5432.py @@ -13,7 +13,6 @@ # limitations under the License. from __future__ import annotations -from botocore.exceptions import ClientError import json import logging @@ -58,7 +57,8 @@ def parse(self, payload): return {"security_group_id": security_group_id}, region def remediate(self, client, security_group_id): - """Block public access to port 5432 for both IPv4 and IPv6. + """Block public access to port 5432 for both IPv4 and IPv6 by + removing all the rules containing port 5432 in the range. :param client: Instance of the AWS boto3 client. :param security_group_id: The ID of the security group. You must specify either the security group ID or the @@ -69,56 +69,40 @@ def remediate(self, client, security_group_id): :rtype: int :raises: botocore.exceptions.ClientError """ - - # Revoke ipv4 permission - logging.info("revoking ivp4 permissions") port = 5432 try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(' CidrIp="0.0.0.0/0"') - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - CidrIp="0.0.0.0/0", - FromPort=port, - GroupId=security_group_id, - IpProtocol="tcp", - ToPort=port, - ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - # Revoke ipv6 permission - logging.info("revoking ivp6 permissions") - try: - logging.info(" executing client.revoke_security_group_ingress") - logging.info(f" FromPort={port}") - logging.info(f" GroupId={security_group_id}") - logging.info(' IpProtocol="tcp"') - logging.info(' "Ipv6Ranges": [{"CidrIpv6": "::/0"}]') - logging.info(f" ToPort={port}") - client.revoke_security_group_ingress( - GroupId=security_group_id, - IpPermissions=[ - { - "FromPort": port, - "IpProtocol": "tcp", - "Ipv6Ranges": [{"CidrIpv6": "::/0"}], - "ToPort": port, - }, - ], + 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, ) - except ClientError as e: - if "InvalidPermission.NotFound" not in str(e): - logging.error(f"{str(e)}") - raise - - logging.info("successfully executed remediation") - + for rule in security_group_rules["SecurityGroupRules"]: + if ( + rule["IpProtocol"] == "tcp" + 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 5432 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): From 12bb9c440d5b6bd949a97ddc85100f9ecfaa0a3d Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Wed, 4 Aug 2021 23:17:28 +0530 Subject: [PATCH 11/23] PLA-29459 - Update Readme and tox file (#104) * PLA-29459 - Update Readme and tox file * PLA-29459 - Updated readme --- README.md | 6 +++++- tox.ini | 54 +++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8e31468..fffc546 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ The table below lists all the supported jobs with their links. | 21. | 688d093c-3b8d-11eb-adc1-0242ac120002 | S3 bucket should allow only HTTPS requests | [aws-s3-bucket-policy-allow-https](remediation_worker/jobs/aws_s3_bucket_policy_allow_https) | | 22. | 09639b9d-98e8-493b-b8a4-916775a7dea9 | SQS queue policy should restricted access to required users | [aws-sqs-queue-publicly-accessible](remediation_worker/jobs/aws_sqs_queue_publicly_accessible) | | 23. | 1ec4a1f2-3e08-11eb-b378-0242ac130002 | Network ACL should restrict administration ports (3389 and 22) from public access | [aws-ec2-administration-ports-ingress-allowed](remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed) | -| 24. | ce603728-d631-4bae-8657-c22da6e5944e | Kinesis data stream should be encrypted +| 24. | ce603728-d631-4bae-8657-c22da6e5944e | Kinesis data stream should be encrypted | [kinesis-encrypt-stream](remediation_worker/jobs/kinesis_encrypt_stream) | | 25. | 5c8c263d7a550e1fb6560c39 | EC2 instance should restrict public access to FTP data port (20) | [ec2-close-port-20](remediation_worker/jobs/ec2_close_port_20) | | 26. | 4823ede0-7bed-4af0-a182-81c2ada80203 | EC2 instance should restrict public access to Kibana (5601) | [ec2-close-port-5601](remediation_worker/jobs/ec2_close_port_5601) | | 27. | 5c8c26427a550e1fb6560c41 | EC2 instance should restrict public access to MySQL server port (3306) | [ec2-close-port-3306](remediation_worker/jobs/ec2_close_port_3306) | @@ -149,6 +149,10 @@ The table below lists all the supported jobs with their links. | 36. | 2cdb8877-7ac3-4483-9ed0-1e792171d125 | EBS volume snapshot should be private | [ebs-private-snapshot](remediation_worker/jobs/ebs_private_snapshot) | | 37. | 5c8c26467a550e1fb6560c48 | RDS instance should restrict public access | [rds-remove-public-endpoint](remediation_worker/jobs/rds_remove_public_endpoint) | | 38. | 5c8c264a7a550e1fb6560c4c | RDS should have automatic minor version upgrades enabled | [rds-enable-version-update](remediation_worker/jobs/rds_enable_version_update) | +| 39. | 5c8c25f37a550e1fb6560bca | EC2 VPC default security group should restrict all access | [aws-ec2-default-security-group-traffic](remediation_worker/jobs/aws_ec2_default_security_group_traffic) | +| 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) | ## 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/tox.ini b/tox.ini index 7d3972a..6b5443e 100644 --- a/tox.ini +++ b/tox.ini @@ -2,17 +2,21 @@ minversion = 3.6.0 skip_missing_interpreters = true envlist = - unit-ec2-close-port-5601 - unit-ec2-close-port-5439 - unit-ec2-close-port-3306 - unit-ec2-close-port-27017 - unit-ec2-close-port-23 - unit-ec2-close-port-21 - unit-ec2-close-port-20 - unit-ec2-close-port-1521 - unit-ec2-close-port-1433 - unit-ec2-close-port-8080 - unit-ec2-close-port-8200-9300 + unit-aws-ec2-default-security-group-traffic + unit-aws-iam-password-policy-min-length + unit-aws-iam-password-reuse-prevention + unit-aws-iam-server-certificate-expired + unit-ec2-close-port-5601 + unit-ec2-close-port-5439 + unit-ec2-close-port-3306 + unit-ec2-close-port-27017 + unit-ec2-close-port-23 + unit-ec2-close-port-21 + unit-ec2-close-port-20 + unit-ec2-close-port-1521 + unit-ec2-close-port-1433 + unit-ec2-close-port-8080 + unit-ec2-close-port-8200-9300 unit-security-group-close-port-5432 unit-s3-remove-public-admin-acl unit-s3-enable-access-logging @@ -52,10 +56,10 @@ envlist = unit-azure-postgresql-allow-access-to-azure-service-disabled unit-aws-s3-bucket-policy-allow-https unit-aws-sqs-queue-publicly-accessible - unit-ebs-private-snapshot - unit-rds-remove-public-endpoint - unit-rds_enable_version_update - unit-kinesis-encrypt-stream + unit-ebs-private-snapshot + unit-rds-remove-public-endpoint + unit-rds_enable_version_update + unit-kinesis-encrypt-stream [testenv] @@ -390,3 +394,23 @@ deps = -r remediation_worker/jobs/ec2_close_port_9200_9300/requirements-dev.txt changedir = test pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_8080.py deps = -r remediation_worker/jobs/ec2_close_port_8080/requirements-dev.txt + +[testenv:unit-aws-ec2-default-security-group-traffic] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_ec2_default_security_group_traffic.py +deps = -r remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements-dev.txt + +[testenv:unit-aws-iam-password-policy-min-length] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_iam_password_policy_min_length.py +deps = -r remediation_worker/jobs/aws_iam_password_policy_min_length/requirements-dev.txt + +[testenv:unit-aws-iam-password-reuse-prevention] +changedir = test +pytest --capture=no --basetemp="{envtmpdir}" unit/test_aws_iam_password_reuse_prevention.py +deps = -r remediation_worker/jobs/aws_iam_password_reuse_prevention/requirements-dev.txt + +[testenv:unit-aws-iam-server-certificate-expired] +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 From 32ac5f105749f4281be1c68c3bea0b5edb1475c0 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Thu, 5 Aug 2021 22:31:37 +0530 Subject: [PATCH 12/23] Fixed requirements file (#105) --- .../requirements.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt index 474dc8e..f01353d 100644 --- a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/requirements.txt @@ -1,6 +1,6 @@ boto3==1.18.4 \ - --hash=sha256:649ed1ca205f5ee0b0328d54580780aebc1a7a05681a24f6ee05253007ca48d8 \ - --hash=sha256:7079b40bd6621c54a0385a8fc11240cff4318a4d487292653e393e18254f5d94 -botocore==1.19.60 \ - --hash=sha256:423a1a9502bd7bc5db8c6e64f9374f64d8ac18e6b870278a9ff65f59d268cd58 \ - --hash=sha256:80dd615a34c7e2c73606070a9358f7b5c1cb0c9989348306c1c9ddff45bb6ebe \ No newline at end of file + --hash=sha256:649ed1ca205f5ee0b0328d54580780aebc1a7a05681a24f6ee05253007ca48d8 \ + --hash=sha256:7079b40bd6621c54a0385a8fc11240cff4318a4d487292653e393e18254f5d94 +botocore==1.21.5 \ + --hash=sha256:0070c5e02b581db40ff5fd1b5e02db90ed88e7e861901894bd78fd998656da68 \ + --hash=sha256:bed34fe7a007180f4208b65515bab1755cdd9fcf2c6720f74ae7ecd2e707f4b7 \ No newline at end of file From e9a67a23d0eb18e0aef32f98dda21af8d28dee13 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Sat, 4 Sep 2021 03:50:09 +0530 Subject: [PATCH 13/23] PLA-28074 - Update py version from 1.9.0 to 1.10.0 (#108) --- .../azure_blob_remove_public_access/requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../azure_key_vault_is_recoverable/requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../jobs/azure_sql_auditing_on_server/requirements-dev.txt | 6 +++--- .../jobs/azure_sql_data_encryption_on/requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../requirements-dev.txt | 6 +++--- .../jobs/azure_vm_close_port_22/requirements-dev.txt | 6 +++--- 23 files changed, 69 insertions(+), 69 deletions(-) diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt b/remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt index 1143bce..f8b5096 100644 --- a/remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt +++ b/remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt @@ -19,9 +19,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_keys/requirements-dev.txt b/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_keys/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_keys/requirements-dev.txt +++ b/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_keys/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_secrets/requirements-dev.txt b/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_secrets/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_secrets/requirements-dev.txt +++ b/remediation_worker/jobs/azure_key_vault_expiry_date_set_for_all_secrets/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_key_vault_is_recoverable/requirements-dev.txt b/remediation_worker/jobs/azure_key_vault_is_recoverable/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_key_vault_is_recoverable/requirements-dev.txt +++ b/remediation_worker/jobs/azure_key_vault_is_recoverable/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt index 4ee3a9a..31cf9de 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt @@ -19,9 +19,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/requirements-dev.txt b/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/requirements-dev.txt +++ b/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt index 1143bce..f8b5096 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt @@ -19,9 +19,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt b/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt index 1143bce..f8b5096 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt @@ -19,9 +19,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/requirements-dev.txt b/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/requirements-dev.txt +++ b/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/requirements-dev.txt b/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/requirements-dev.txt +++ b/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt index 594637e..cf96f62 100644 --- a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt @@ -19,9 +19,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/requirements-dev.txt b/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/requirements-dev.txt index 3535fa9..941e79c 100644 --- a/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/requirements-dev.txt +++ b/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt b/remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/requirements-dev.txt b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/requirements-dev.txt +++ b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt b/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/requirements-dev.txt b/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/requirements-dev.txt +++ b/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt index c7e0d39..d7306fc 100644 --- a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt @@ -19,9 +19,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt b/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt +++ b/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt index 759f2ca..196b977 100644 --- a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt index 8be29aa..92b7326 100644 --- a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_storage_trusted_microsoft_services_access_enabled/requirements-dev.txt b/remediation_worker/jobs/azure_storage_trusted_microsoft_services_access_enabled/requirements-dev.txt index 0c3049a..830c4b9 100644 --- a/remediation_worker/jobs/azure_storage_trusted_microsoft_services_access_enabled/requirements-dev.txt +++ b/remediation_worker/jobs/azure_storage_trusted_microsoft_services_access_enabled/requirements-dev.txt @@ -16,9 +16,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt b/remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt index 1143bce..f8b5096 100644 --- a/remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt +++ b/remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt @@ -19,9 +19,9 @@ packaging==20.4 \ pluggy==0.13.1 \ --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d -py==1.9.0 \ - --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ - --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +py==1.10.0 \ + --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ + --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a pyparsing==2.4.7 \ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b From 3f0183d588418516aa1650e8180f4021913f8609 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Sat, 4 Sep 2021 03:50:34 +0530 Subject: [PATCH 14/23] Fix import issues in azure jobs (#107) --- .../azure_mysql_enforce_ssl_connection_enable.py | 1 + .../azure_postgresql_allow_access_to_azure_service_disabled.py | 1 + .../azure_postgresql_enforce_ssl_connection_enable.py | 1 + .../azure_sql_auditing_on_server/azure_sql_auditing_on_server.py | 1 + .../azure_sql_tde_protector_encrypted_cmk.py | 1 + .../azure_sql_threat_detection_on_server.py | 1 + .../azure_sql_threat_detection_types_all_server.py | 1 + 7 files changed, 7 insertions(+) diff --git a/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py b/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py index c6366c1..e3b8ccd 100644 --- a/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py +++ b/remediation_worker/jobs/azure_mysql_enforce_ssl_connection_enable/azure_mysql_enforce_ssl_connection_enable.py @@ -2,6 +2,7 @@ import os import sys import logging +import time from azure.mgmt.rdbms.mysql import MySQLManagementClient from azure.identity import ClientSecretCredential diff --git a/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py b/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py index 084ff74..caae573 100644 --- a/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py +++ b/remediation_worker/jobs/azure_postgresql_allow_access_to_azure_service_disabled/azure_postgresql_allow_access_to_azure_service_disabled.py @@ -2,6 +2,7 @@ import os import sys import logging +import time from azure.mgmt.rdbms.postgresql import PostgreSQLManagementClient from azure.identity import ClientSecretCredential diff --git a/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py b/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py index e0f0fb0..048d8ff 100644 --- a/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py +++ b/remediation_worker/jobs/azure_postgresql_enforce_ssl_connection_enable/azure_postgresql_enforce_ssl_connection_enable.py @@ -2,6 +2,7 @@ import os import sys import logging +import time from azure.mgmt.rdbms.postgresql import PostgreSQLManagementClient from azure.identity import ClientSecretCredential diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index a90691f..a8f67f0 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -4,6 +4,7 @@ import logging import uuid import datetime +import time from dateutil import parser as date_parse from typing import List diff --git a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py index f7c437f..0ecfa7d 100644 --- a/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py +++ b/remediation_worker/jobs/azure_sql_tde_protector_encrypted_cmk/azure_sql_tde_protector_encrypted_cmk.py @@ -3,6 +3,7 @@ import sys import logging import datetime +import time from dateutil import parser as date_parse from typing import List diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py b/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py index f1f9f31..24996da 100644 --- a/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py @@ -2,6 +2,7 @@ import os import sys import logging +import time from azure.mgmt.sql import SqlManagementClient from azure.common.credentials import ServicePrincipalCredentials diff --git a/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py b/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py index 292155d..227f7e1 100644 --- a/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py +++ b/remediation_worker/jobs/azure_sql_threat_detection_types_all_server/azure_sql_threat_detection_types_all_server.py @@ -2,6 +2,7 @@ import os import sys import logging +import time from azure.mgmt.sql import SqlManagementClient from azure.common.credentials import ServicePrincipalCredentials From 794a73192f942268b287754b1f964c7e06c7c215 Mon Sep 17 00:00:00 2001 From: sreedevikr <90842270+sreedevikr@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:10:59 +0530 Subject: [PATCH 15/23] Initial commit for aws s3 remove full access to authenticated users (#118) * Initial commit for aws s3 remove full access to authenticated users * Correct the ruleid, minimum permissions, removed the botocore.exceptions.ClientError as put_bucket_acl doesnot throw exception and created test case * Updated the main readme and tox.ini file * Included a try catch exception block in the remediation job and added test case for exception case --- README.md | 2 + .../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 + ...s3_remove_fullaccess_authenticatedusers.py | 232 ++++++++++++++++++ tox.ini | 7 + 10 files changed, 481 insertions(+) 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 100644 test/unit/test_aws_s3_remove_fullaccess_authenticatedusers.py diff --git a/README.md b/README.md index fffc546..3633c20 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,8 @@ 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. | 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_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_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..92cdade 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,8 @@ envlist = unit-rds-remove-public-endpoint unit-rds_enable_version_update unit-kinesis-encrypt-stream + unit-aws_s3_remove_fullaccess_authenticatedusers + [testenv] @@ -414,3 +416,8 @@ 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_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 From 416e12183d6aac94d998aa08f3e28325f10771ac Mon Sep 17 00:00:00 2001 From: sreedevikr <90842270+sreedevikr@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:21:59 +0530 Subject: [PATCH 16/23] Aws rds snapshot remove publicaccess (#117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Release/v1.8.0 (#106) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, … (#93) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, 3306, 5439, 5601, 8080, 9200 and 9300 * Checking in unit test and tox.ini, made modification to remove common pkg from ec2_close_port_20.py * Checking in README with addition aws close port rules * Update README with correct port names for the new scripts * PLA-26195 - Handled PrincipalNotFound Exception in sql auditing job (#98) * PLA-24844 - Remediation job to restrict default security group access (#85) * PLA-24844 - Remediation job to restrict default security group access * PLA-24844 - Remediation job to restrict default security group access * Updated the remediation job code * PLA-25429 - Remediation job to set password reuse prevention policy (#89) * PLA-25429 - Remediation job to set password reuse prevention policy * PLA-25429 - Updated unit test * Updated the remediation job code * PLA-25428 - Remediation Job to set minimum password length (#90) * PLA-25430 - Remediation Job to delete expired server certificate (#96) * Initial commit for kinesis_encrypt_stream (#97) * Initial commit for kinesis_encrypt_stream * modified to add a return and exception to kinesis_encrypt_stream.py and unit testcases for remediate * remove print * update README.md * update README.md * remove format in kinesis_encrypt_stream.py * update README with a correct instruction to run the script and add a missing error loggin Co-authored-by: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> * PLA-26855 - Updated azure remediation jobs to wait for the poller result (#99) * PLA-26855 - Updated azure remediation jobs to wait for the poller result * PLA-26855 - Update azure jobs to poll continuously and log the status * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_versi… (#101) * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_version_update, rds_remove_public_endpoint * Update ebs_private_snapshot.py * Incorporated comments and inputs from PR review * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * PLA-29176 - Fix remediation jobs for port rules (#102) * PLA-29176 - Fix remediation jobs for port rules * PLA-29176 - updated requirements * PLA-29176 - Updated the public instance port remediation jobs * PLA-29176 - Fixed readme file * PLA-29176 - Fixed comments * PLA-29176 - Updated all the AWS port rule remediation jobs * PLA-29176 - Fixed requirements-dev file * PLA-29176 - Added comments * PLA-29459 - Update Readme and tox file (#104) * PLA-29459 - Update Readme and tox file * PLA-29459 - Updated readme * Fixed requirements file (#105) Co-authored-by: lytran2000 <44222483+lytran2000@users.noreply.github.com> * Release/v1.9.0 (#113) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, … (#93) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, 3306, 5439, 5601, 8080, 9200 and 9300 * Checking in unit test and tox.ini, made modification to remove common pkg from ec2_close_port_20.py * Checking in README with addition aws close port rules * Update README with correct port names for the new scripts * PLA-26195 - Handled PrincipalNotFound Exception in sql auditing job (#98) * PLA-24844 - Remediation job to restrict default security group access (#85) * PLA-24844 - Remediation job to restrict default security group access * PLA-24844 - Remediation job to restrict default security group access * Updated the remediation job code * PLA-25429 - Remediation job to set password reuse prevention policy (#89) * PLA-25429 - Remediation job to set password reuse prevention policy * PLA-25429 - Updated unit test * Updated the remediation job code * PLA-25428 - Remediation Job to set minimum password length (#90) * PLA-25430 - Remediation Job to delete expired server certificate (#96) * Initial commit for kinesis_encrypt_stream (#97) * Initial commit for kinesis_encrypt_stream * modified to add a return and exception to kinesis_encrypt_stream.py and unit testcases for remediate * remove print * update README.md * update README.md * remove format in kinesis_encrypt_stream.py * update README with a correct instruction to run the script and add a missing error loggin Co-authored-by: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> * PLA-26855 - Updated azure remediation jobs to wait for the poller result (#99) * PLA-26855 - Updated azure remediation jobs to wait for the poller result * PLA-26855 - Update azure jobs to poll continuously and log the status * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_versi… (#101) * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_version_update, rds_remove_public_endpoint * Update ebs_private_snapshot.py * Incorporated comments and inputs from PR review * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * PLA-29176 - Fix remediation jobs for port rules (#102) * PLA-29176 - Fix remediation jobs for port rules * PLA-29176 - updated requirements * PLA-29176 - Updated the public instance port remediation jobs * PLA-29176 - Fixed readme file * PLA-29176 - Fixed comments * PLA-29176 - Updated all the AWS port rule remediation jobs * PLA-29176 - Fixed requirements-dev file * PLA-29176 - Added comments * PLA-29459 - Update Readme and tox file (#104) * PLA-29459 - Update Readme and tox file * PLA-29459 - Updated readme * Fixed requirements file (#105) * PLA-28074 - Update py version from 1.9.0 to 1.10.0 (#108) * Fix import issues in azure jobs (#107) Co-authored-by: lytran2000 <44222483+lytran2000@users.noreply.github.com> * Initial commit for aws rds snapshots remove public access * Updated the filename and testing steps in the readme * Corrected the ruleid, added unit test and modified the remediation logic to change the errorcode to the snapshot relevant errorcode and check if all is present in the attribute value list instead of direct equality check as attribute values is a list * Updated tox.ini * Corrected the ruleid in the remediation readme Co-authored-by: Vikramjeet Singh <58273802+vikramsinghvirdi@users.noreply.github.com> Co-authored-by: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Co-authored-by: lytran2000 <44222483+lytran2000@users.noreply.github.com> --- README.md | 3 +- .../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 + ...st_aws_rds_snapshot_remove_publicaccess.py | 78 +++++++++ tox.ini | 6 + 10 files changed, 388 insertions(+), 1 deletion(-) 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 test/unit/test_aws_rds_snapshot_remove_publicaccess.py diff --git a/README.md b/README.md index 3633c20..8892550 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,8 @@ 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. | 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) | +| 43. | 5c8c26487a550e1fb6560c4a | RDS snapshot should restrict public access | [aws-rds-snapshot-remove-publicaccess](remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess) | +| 44. | 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 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..843f426 --- /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(DBInstanceIdentifier=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( + DBInstanceIdentifier=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/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/tox.ini b/tox.ini index 92cdade..ba69f2b 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,7 @@ envlist = unit-rds-remove-public-endpoint unit-rds_enable_version_update unit-kinesis-encrypt-stream + unit-aws-rds-snapshot-remove-publicaccess unit-aws_s3_remove_fullaccess_authenticatedusers @@ -417,6 +418,11 @@ 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-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 From 5c1c872964344c3f41a527e65ccbfbc434f3a404 Mon Sep 17 00:00:00 2001 From: sreedevikr <90842270+sreedevikr@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:24:45 +0530 Subject: [PATCH 17/23] Aws ec2 close port 11211 (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Release/v1.8.0 (#106) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, … (#93) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, 3306, 5439, 5601, 8080, 9200 and 9300 * Checking in unit test and tox.ini, made modification to remove common pkg from ec2_close_port_20.py * Checking in README with addition aws close port rules * Update README with correct port names for the new scripts * PLA-26195 - Handled PrincipalNotFound Exception in sql auditing job (#98) * PLA-24844 - Remediation job to restrict default security group access (#85) * PLA-24844 - Remediation job to restrict default security group access * PLA-24844 - Remediation job to restrict default security group access * Updated the remediation job code * PLA-25429 - Remediation job to set password reuse prevention policy (#89) * PLA-25429 - Remediation job to set password reuse prevention policy * PLA-25429 - Updated unit test * Updated the remediation job code * PLA-25428 - Remediation Job to set minimum password length (#90) * PLA-25430 - Remediation Job to delete expired server certificate (#96) * Initial commit for kinesis_encrypt_stream (#97) * Initial commit for kinesis_encrypt_stream * modified to add a return and exception to kinesis_encrypt_stream.py and unit testcases for remediate * remove print * update README.md * update README.md * remove format in kinesis_encrypt_stream.py * update README with a correct instruction to run the script and add a missing error loggin Co-authored-by: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> * PLA-26855 - Updated azure remediation jobs to wait for the poller result (#99) * PLA-26855 - Updated azure remediation jobs to wait for the poller result * PLA-26855 - Update azure jobs to poll continuously and log the status * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_versi… (#101) * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_version_update, rds_remove_public_endpoint * Update ebs_private_snapshot.py * Incorporated comments and inputs from PR review * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * PLA-29176 - Fix remediation jobs for port rules (#102) * PLA-29176 - Fix remediation jobs for port rules * PLA-29176 - updated requirements * PLA-29176 - Updated the public instance port remediation jobs * PLA-29176 - Fixed readme file * PLA-29176 - Fixed comments * PLA-29176 - Updated all the AWS port rule remediation jobs * PLA-29176 - Fixed requirements-dev file * PLA-29176 - Added comments * PLA-29459 - Update Readme and tox file (#104) * PLA-29459 - Update Readme and tox file * PLA-29459 - Updated readme * Fixed requirements file (#105) Co-authored-by: lytran2000 <44222483+lytran2000@users.noreply.github.com> * Release/v1.9.0 (#113) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, … (#93) * Initial commit for ec2 close port for 1433, 1521, 20, 21, 23, 27017, 3306, 5439, 5601, 8080, 9200 and 9300 * Checking in unit test and tox.ini, made modification to remove common pkg from ec2_close_port_20.py * Checking in README with addition aws close port rules * Update README with correct port names for the new scripts * PLA-26195 - Handled PrincipalNotFound Exception in sql auditing job (#98) * PLA-24844 - Remediation job to restrict default security group access (#85) * PLA-24844 - Remediation job to restrict default security group access * PLA-24844 - Remediation job to restrict default security group access * Updated the remediation job code * PLA-25429 - Remediation job to set password reuse prevention policy (#89) * PLA-25429 - Remediation job to set password reuse prevention policy * PLA-25429 - Updated unit test * Updated the remediation job code * PLA-25428 - Remediation Job to set minimum password length (#90) * PLA-25430 - Remediation Job to delete expired server certificate (#96) * Initial commit for kinesis_encrypt_stream (#97) * Initial commit for kinesis_encrypt_stream * modified to add a return and exception to kinesis_encrypt_stream.py and unit testcases for remediate * remove print * update README.md * update README.md * remove format in kinesis_encrypt_stream.py * update README with a correct instruction to run the script and add a missing error loggin Co-authored-by: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> * PLA-26855 - Updated azure remediation jobs to wait for the poller result (#99) * PLA-26855 - Updated azure remediation jobs to wait for the poller result * PLA-26855 - Update azure jobs to poll continuously and log the status * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_versi… (#101) * Initial commit for aws 3 jobs: ebs_private_snapshot, rds_enable_version_update, rds_remove_public_endpoint * Update ebs_private_snapshot.py * Incorporated comments and inputs from PR review * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * PLA-29176 - Fix remediation jobs for port rules (#102) * PLA-29176 - Fix remediation jobs for port rules * PLA-29176 - updated requirements * PLA-29176 - Updated the public instance port remediation jobs * PLA-29176 - Fixed readme file * PLA-29176 - Fixed comments * PLA-29176 - Updated all the AWS port rule remediation jobs * PLA-29176 - Fixed requirements-dev file * PLA-29176 - Added comments * PLA-29459 - Update Readme and tox file (#104) * PLA-29459 - Update Readme and tox file * PLA-29459 - Updated readme * Fixed requirements file (#105) * PLA-28074 - Update py version from 1.9.0 to 1.10.0 (#108) * Fix import issues in azure jobs (#107) Co-authored-by: lytran2000 <44222483+lytran2000@users.noreply.github.com> * Initial commit for aws ec2 close port 11211 * Updated readme file to reflect the correct python file name * Updates to readme to use the relevant python file * Update the ruleid to use the right one and create unit test case for closing port 11211 for ec2 * Updated the main readme and the tox file * Fixed the readme file by adding back the aws_iam_server_certificate_expired block and then including the aws_ec2_close_port_11211 Co-authored-by: Vikramjeet Singh <58273802+vikramsinghvirdi@users.noreply.github.com> Co-authored-by: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Co-authored-by: lytran2000 <44222483+lytran2000@users.noreply.github.com> --- README.md | 5 +- .../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 + test/unit/test_aws_ec2_close_port_11211.py | 39 ++++++ tox.ini | 6 + 10 files changed, 302 insertions(+), 2 deletions(-) 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 100755 test/unit/test_aws_ec2_close_port_11211.py diff --git a/README.md b/README.md index 8892550..bd63ebb 100644 --- a/README.md +++ b/README.md @@ -153,8 +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. | 5c8c26487a550e1fb6560c4a | RDS snapshot should restrict public access | [aws-rds-snapshot-remove-publicaccess](remediation_worker/jobs/aws_rds_snapshot_remove_publicaccess) | -| 44. | 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) | +| 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 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..8c63dba --- /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"] == "tcp" + 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/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/tox.ini b/tox.ini index ba69f2b..f7def64 100644 --- a/tox.ini +++ b/tox.ini @@ -60,6 +60,7 @@ 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 @@ -418,6 +419,11 @@ 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 From 9ae113f2da58a0470a1516d8694840459243b5e4 Mon Sep 17 00:00:00 2001 From: sreedevikr <90842270+sreedevikr@users.noreply.github.com> Date: Tue, 5 Oct 2021 21:48:58 +0530 Subject: [PATCH 18/23] Modified the remediation logic to check for protocol udp instead of tcp as the remediation is for closing the open udp port for memcache (#119) --- .../jobs/aws_ec2_close_port_11211/aws_ec2_close_port_11211.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 8c63dba..89c5578 100644 --- 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 @@ -84,7 +84,7 @@ def remediate(self, client, instance_id): ) for rule in security_group_rules["SecurityGroupRules"]: if ( - rule["IpProtocol"] == "tcp" + rule["IpProtocol"] == "udp" and rule["IsEgress"] is False and rule["FromPort"] <= port and rule["ToPort"] >= port From aac016e977f67ab0bd224c2836fefcdc0f37e452 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Tue, 1 Feb 2022 05:12:28 +0530 Subject: [PATCH 19/23] Fixed RDS Snapshot remove public access remediation job (#120) --- .../aws_rds_snapshot_remove_publicaccess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 843f426..764214b 100644 --- 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 @@ -78,7 +78,7 @@ def remediate(self, client, snapshot_identifier): # 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(DBInstanceIdentifier=snapshot_identifier).get('DBSnapshotAttributesResult') + 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 @@ -92,7 +92,7 @@ def remediate(self, client, snapshot_identifier): logging.info("executing client.modify_db_snapshot_attribute and apply immediately") logging.info("Attribute=Restore,Remove all AttributeValue") client.modify_db_snapshot_attribute( - DBInstanceIdentifier=snapshot_identifier, + DBSnapshotIdentifier=snapshot_identifier, AttributeName="restore",ValuesToRemove=["all"] ) logging.info( From 8ae846d170590001e03099e37fcc537a9e353a42 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Thu, 31 Mar 2022 00:11:41 +0530 Subject: [PATCH 20/23] PLA-35232 - Fixed remediation jobs that does not report failures (#124) --- .../aws_cloudtrail_logs_encrypted.py | 1 + .../aws_ec2_administration_ports_ingress_allowed.py | 1 + .../jobs/aws_ec2_close_port_11211/aws_ec2_close_port_11211.py | 1 + .../aws_ec2_default_security_group_traffic.py | 1 + .../aws_iam_password_policy_min_length.py | 1 + .../aws_iam_password_reuse_prevention.py | 1 + .../jobs/aws_kms_key_rotates/aws_kms_key_rotates.py | 1 + .../jobs/ec2_close_port_1433/ec2_close_port_1433.py | 1 + .../jobs/ec2_close_port_1521/ec2_close_port_1521.py | 1 + remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py | 1 + remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py | 1 + remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py | 1 + remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py | 1 + .../jobs/ec2_close_port_27017/ec2_close_port_27017.py | 1 + .../jobs/ec2_close_port_3306/ec2_close_port_3306.py | 1 + .../jobs/ec2_close_port_3389/ec2_close_port_3389.py | 1 + .../jobs/ec2_close_port_5439/ec2_close_port_5439.py | 1 + .../jobs/ec2_close_port_5601/ec2_close_port_5601.py | 1 + .../jobs/ec2_close_port_8080/ec2_close_port_8080.py | 1 + .../jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py | 1 + .../jobs/s3_enable_access_logging/s3_enable_access_logging.py | 1 + .../security_group_close_port_22/security_group_close_port_22.py | 1 + .../security_group_close_port_3389.py | 1 + .../security_group_close_port_5432.py | 1 + 24 files changed, 24 insertions(+) diff --git a/remediation_worker/jobs/aws_cloudtrail_logs_encrypted/aws_cloudtrail_logs_encrypted.py b/remediation_worker/jobs/aws_cloudtrail_logs_encrypted/aws_cloudtrail_logs_encrypted.py index f1f6257..a1a27aa 100644 --- a/remediation_worker/jobs/aws_cloudtrail_logs_encrypted/aws_cloudtrail_logs_encrypted.py +++ b/remediation_worker/jobs/aws_cloudtrail_logs_encrypted/aws_cloudtrail_logs_encrypted.py @@ -174,6 +174,7 @@ def remediate(self, region, s3_client, cloudtrail_client, cloudtrail_name, cloud logging.info("successfully completed remediation job") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py index 1659c68..00ee5e9 100644 --- a/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py +++ b/remediation_worker/jobs/aws_ec2_administration_ports_ingress_allowed/aws_ec2_administration_ports_ingress_allowed.py @@ -117,6 +117,7 @@ def remediate(self, region, client, network_acl_id, cloud_account_id): logging.info("successfully completed remediation job") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): 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 index 89c5578..e8cc0b5 100644 --- 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 @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py index c40a97d..73cf384 100644 --- a/remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py +++ b/remediation_worker/jobs/aws_ec2_default_security_group_traffic/aws_ec2_default_security_group_traffic.py @@ -100,6 +100,7 @@ def remediate(self, client, security_group_id, region, cloud_account_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 diff --git a/remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py b/remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py index 0c07e4b..642bfaf 100644 --- a/remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py +++ b/remediation_worker/jobs/aws_iam_password_policy_min_length/aws_iam_password_policy_min_length.py @@ -71,6 +71,7 @@ def remediate(self, client, account_id): logging.info("successfully completed remediation job") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py b/remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py index c7a0f3c..9480920 100644 --- a/remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py +++ b/remediation_worker/jobs/aws_iam_password_reuse_prevention/aws_iam_password_reuse_prevention.py @@ -71,6 +71,7 @@ def remediate(self, client, account_id): logging.info("successfully completed remediation job") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/aws_kms_key_rotates/aws_kms_key_rotates.py b/remediation_worker/jobs/aws_kms_key_rotates/aws_kms_key_rotates.py index 90856bf..fae6987 100644 --- a/remediation_worker/jobs/aws_kms_key_rotates/aws_kms_key_rotates.py +++ b/remediation_worker/jobs/aws_kms_key_rotates/aws_kms_key_rotates.py @@ -82,6 +82,7 @@ def remediate(self, kms_client, key_id, region): logging.info("successfully completed remediation job") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py b/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py index 189efe1..a694d1f 100644 --- a/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py +++ b/remediation_worker/jobs/ec2_close_port_1433/ec2_close_port_1433.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py b/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py index e7f3832..e58003e 100644 --- a/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py +++ b/remediation_worker/jobs/ec2_close_port_1521/ec2_close_port_1521.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py b/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py index 9f0bd1d..830a88f 100644 --- a/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py +++ b/remediation_worker/jobs/ec2_close_port_20/ec2_close_port_20.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py b/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py index 53c87f4..aa7ccf5 100644 --- a/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py +++ b/remediation_worker/jobs/ec2_close_port_21/ec2_close_port_21.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py b/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py index 51ee27a..b43786b 100644 --- a/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py +++ b/remediation_worker/jobs/ec2_close_port_22/ec2_close_port_22.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py b/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py index 69a28bc..5e55916 100644 --- a/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py +++ b/remediation_worker/jobs/ec2_close_port_23/ec2_close_port_23.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py b/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py index 85f1bea..4c65d43 100644 --- a/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py +++ b/remediation_worker/jobs/ec2_close_port_27017/ec2_close_port_27017.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py b/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py index a7f72bf..425e128 100644 --- a/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py +++ b/remediation_worker/jobs/ec2_close_port_3306/ec2_close_port_3306.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py b/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py index 1e4344b..8eba389 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py +++ b/remediation_worker/jobs/ec2_close_port_3389/ec2_close_port_3389.py @@ -110,6 +110,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py b/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py index 77802c5..f8394a7 100644 --- a/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py +++ b/remediation_worker/jobs/ec2_close_port_5439/ec2_close_port_5439.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py b/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py index 2b19fbc..a2130ab 100644 --- a/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py +++ b/remediation_worker/jobs/ec2_close_port_5601/ec2_close_port_5601.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py b/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py index 75865c5..acd0bb7 100644 --- a/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py +++ b/remediation_worker/jobs/ec2_close_port_8080/ec2_close_port_8080.py @@ -109,6 +109,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py b/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py index b3979c1..5160ae7 100644 --- a/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py +++ b/remediation_worker/jobs/ec2_close_port_9200_9300/ec2_close_port_9200_9300.py @@ -110,6 +110,7 @@ def remediate(self, client, instance_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 diff --git a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py index 60d814f..c0b95fd 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py +++ b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py @@ -246,6 +246,7 @@ def remediate(self, region, client, source_bucket, target_bucket, target_prefix) logging.info("successfully completed remediation job") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/security_group_close_port_22/security_group_close_port_22.py b/remediation_worker/jobs/security_group_close_port_22/security_group_close_port_22.py index 91a4b7d..2780560 100644 --- a/remediation_worker/jobs/security_group_close_port_22/security_group_close_port_22.py +++ b/remediation_worker/jobs/security_group_close_port_22/security_group_close_port_22.py @@ -96,6 +96,7 @@ def remediate(self, client, security_group_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/security_group_close_port_3389/security_group_close_port_3389.py b/remediation_worker/jobs/security_group_close_port_3389/security_group_close_port_3389.py index 1a27438..8e57419 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/security_group_close_port_3389.py +++ b/remediation_worker/jobs/security_group_close_port_3389/security_group_close_port_3389.py @@ -104,6 +104,7 @@ def remediate(self, client, security_group_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): diff --git a/remediation_worker/jobs/security_group_close_port_5432/security_group_close_port_5432.py b/remediation_worker/jobs/security_group_close_port_5432/security_group_close_port_5432.py index acdbfcc..7656497 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/security_group_close_port_5432.py +++ b/remediation_worker/jobs/security_group_close_port_5432/security_group_close_port_5432.py @@ -103,6 +103,7 @@ def remediate(self, client, security_group_id): logging.info("successfully executed remediation") except Exception as e: logging.error(f"{str(e)}") + raise return 0 def run(self, args): From 966d98dd002f484233a1ecca8c6207721a3ad57a Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Wed, 6 Apr 2022 15:28:00 +0530 Subject: [PATCH 21/23] PLA-38601 - Fixed azure remediation jobs to wait for the poller result (#125) --- .../azure_network_security_group_close_port_22.py | 8 +++++++- .../azure_network_security_group_close_port_3389.py | 8 +++++++- .../azure_security_center_enable_ddos_protection.py | 8 +++++++- .../azure_security_udp_access_restricted_from_internet.py | 8 +++++++- .../jobs/azure_vm_close_port_22/azure_vm_close_port_22.py | 8 +++++++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py index a79a83e..3b25837 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py @@ -16,6 +16,7 @@ import os import sys import logging +import time from azure.mgmt.network import NetworkManagementClient from azure.common.credentials import ServicePrincipalCredentials @@ -121,9 +122,14 @@ def remediate(self, client, resource_group_name, security_group_name): ) logging.info(f" resource_group_name={resource_group_name}") logging.info(f" network_security_group_name={security_group_name}") - client.network_security_groups.create_or_update( + poller = client.network_security_groups.create_or_update( resource_group_name, security_group_name, network_security_group ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py index 671e38a..b6a3328 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py @@ -16,6 +16,7 @@ import os import sys import logging +import time from azure.mgmt.network import NetworkManagementClient from azure.common.credentials import ServicePrincipalCredentials @@ -118,9 +119,14 @@ def remediate(self, client, resource_group_name, security_group_name): ) logging.info(f" resource_group_name={resource_group_name}") logging.info(f" network_security_group_name={security_group_name}") - client.network_security_groups.create_or_update( + poller = client.network_security_groups.create_or_update( resource_group_name, security_group_name, network_security_group ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py index 54fe07e..9b8f20a 100644 --- a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py @@ -16,6 +16,7 @@ import os import sys import logging +import time from azure.mgmt.network import NetworkManagementClient from azure.mgmt.network.models import DdosProtectionPlanListResult, SubResource @@ -117,11 +118,16 @@ def remediate( logging.info(f" resource_group_name={resource_group_name}") logging.info(f" virtual_network_name={virtual_network_name}") - client.virtual_networks.begin_create_or_update( + poller = client.virtual_networks.begin_create_or_update( resource_group_name=resource_group_name, virtual_network_name=virtual_network_name, parameters=virtual_network, ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/azure_security_udp_access_restricted_from_internet.py b/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/azure_security_udp_access_restricted_from_internet.py index 8071dec..1451fd4 100644 --- a/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/azure_security_udp_access_restricted_from_internet.py +++ b/remediation_worker/jobs/azure_security_udp_access_restricted_from_internet/azure_security_udp_access_restricted_from_internet.py @@ -2,6 +2,7 @@ import os import sys import logging +import time from azure.mgmt.network import NetworkManagementClient from azure.identity import ClientSecretCredential @@ -102,11 +103,16 @@ def remediate(self, client, resource_group_name, network_security_group_name): f" network_security_group_name={network_security_group_name}" ) logging.info(f" security_rule_name={rule.name}") - client.security_rules.begin_delete( + poller = client.security_rules.begin_delete( resource_group_name=resource_group_name, network_security_group_name=network_security_group_name, security_rule_name=rule.name, ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() except Exception as e: logging.error(f"{str(e)}") raise diff --git a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py index d6c0a0a..953f669 100644 --- a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py +++ b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py @@ -16,6 +16,7 @@ import os import sys import logging +import time from azure.mgmt.compute import ComputeManagementClient from azure.mgmt.network import NetworkManagementClient @@ -123,9 +124,14 @@ def remediate(self, compute_client, network_client, resource_group_name, vm_name ) logging.info(f" resource_group_name={resource_group_name}") logging.info(f" network_security_group_name={security_group_name}") - network_client.network_security_groups.create_or_update( + poller = network_client.network_security_groups.create_or_update( resource_group_name, security_group_name, network_security_group ) + while not poller.done(): + time.sleep(5) + status = poller.status() + logging.info(f"The remediation job status: {status}") + poller.result() except Exception as e: logging.error(f"{str(e)}") raise From 52daae694c133b208932089dd9ae7fcfd2758e7a Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Fri, 13 May 2022 23:45:13 +0530 Subject: [PATCH 22/23] PLA-38601 - Fixed azure security port jobs (#128) * PLA-38601 - Fixed azure remediation jobs to wait for the poller result * PLA-38601 - Add all the required source checks for azure security group port rules --- ...re_network_security_group_close_port_22.py | 45 ++++++++++-------- ..._network_security_group_close_port_3389.py | 46 +++++++++++-------- .../azure_vm_close_port_22.py | 30 +++++++----- 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py index 3b25837..06ff7b8 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py @@ -23,6 +23,7 @@ logging.basicConfig(level=logging.INFO) +source_address_list = ["*", "Internet", "0.0.0.0/0", "0.0.0.0", "/0", "::/0"] class NetworkSecurityGroupClosePort22(object): def parse(self, payload): @@ -91,26 +92,32 @@ def remediate(self, client, resource_group_name, security_group_name): for rule in security_rules: if ( - rule.access != "Allow" - or rule.direction != "Inbound" - or rule.source_address_prefix != "*" + rule.protocol in ["*", "TCP"] + and rule.direction == "Inbound" + and rule.access == "Allow" + and ( + rule.source_address_prefix in source_address_list + or any( + item in rule.source_address_prefixes + for item in source_address_list + ) + ) ): - continue - if rule.destination_port_range is not None: - port_range = rule.destination_port_range - if "-" in port_range: - new_ranges = self._find_and_remove_port([port_range], port) - if len(new_ranges) == 1: - rule.destination_port_range = new_ranges[0] - else: - rule.destination_port_range = None - rule.destination_port_ranges = new_ranges - elif int(rule.destination_port_range) == port: - security_rules.remove(rule) - else: - port_ranges = rule.destination_port_ranges - new_ranges = self._find_and_remove_port(port_ranges, port) - rule.destination_port_ranges = new_ranges + if rule.destination_port_range is not None: + port_range = rule.destination_port_range + if "-" in port_range: + new_ranges = self._find_and_remove_port([port_range], port) + if len(new_ranges) == 1: + rule.destination_port_range = new_ranges[0] + else: + rule.destination_port_range = None + rule.destination_port_ranges = new_ranges + elif int(rule.destination_port_range) == port: + security_rules.remove(rule) + else: + port_ranges = rule.destination_port_ranges + new_ranges = self._find_and_remove_port(port_ranges, port) + rule.destination_port_ranges = new_ranges network_security_group.security_rules = security_rules diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py index b6a3328..c1bc684 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py @@ -23,6 +23,8 @@ logging.basicConfig(level=logging.INFO) +source_address_list = ["*", "Internet", "0.0.0.0/0", "0.0.0.0", "/0", "::/0"] + class NetworkSecurityGroupClosePort3389(object): def parse(self, payload): @@ -88,26 +90,32 @@ def remediate(self, client, resource_group_name, security_group_name): for rule in security_rules: if ( - rule.access != "Allow" - or rule.direction != "Inbound" - or rule.source_address_prefix != "*" + rule.protocol in ["*", "TCP"] + and rule.direction == "Inbound" + and rule.access == "Allow" + and ( + rule.source_address_prefix in source_address_list + or any( + item in rule.source_address_prefixes + for item in source_address_list + ) + ) ): - continue - if rule.destination_port_range is not None: - port_range = rule.destination_port_range - if "-" in port_range: - new_ranges = self._find_and_remove_port([port_range], port) - if len(new_ranges) == 1: - rule.destination_port_range = new_ranges[0] - else: - rule.destination_port_range = None - rule.destination_port_ranges = new_ranges - elif int(rule.destination_port_range) == port: - security_rules.remove(rule) - else: - port_ranges = rule.destination_port_ranges - new_ranges = self._find_and_remove_port(port_ranges, port) - rule.destination_port_ranges = new_ranges + if rule.destination_port_range is not None: + port_range = rule.destination_port_range + if "-" in port_range: + new_ranges = self._find_and_remove_port([port_range], port) + if len(new_ranges) == 1: + rule.destination_port_range = new_ranges[0] + else: + rule.destination_port_range = None + rule.destination_port_ranges = new_ranges + elif int(rule.destination_port_range) == port: + security_rules.remove(rule) + else: + port_ranges = rule.destination_port_ranges + new_ranges = self._find_and_remove_port(port_ranges, port) + rule.destination_port_ranges = new_ranges network_security_group.security_rules = security_rules diff --git a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py index 953f669..719e6f4 100644 --- a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py +++ b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py @@ -24,6 +24,8 @@ logging.basicConfig(level=logging.INFO) +source_address_list = ["*", "Internet", "0.0.0.0/0", "0.0.0.0", "/0", "::/0"] + class VMSecurityGroupClosePort22(object): def parse(self, payload): @@ -101,18 +103,24 @@ def remediate(self, compute_client, network_client, resource_group_name, vm_name for rule in security_rules: if ( - rule.access != "Allow" - or rule.direction != "Inbound" - or rule.source_address_prefix != "*" + rule.protocol in ["*", "TCP"] + and rule.direction == "Inbound" + and rule.access == "Allow" + and ( + rule.source_address_prefix in source_address_list + or any( + item in rule.source_address_prefixes + for item in source_address_list + ) + ) ): - continue - if rule.destination_port_range is not None: - if int(rule.destination_port_range) == port: - security_rules.remove(rule) - else: - port_ranges = rule.destination_port_ranges - new_ranges = self._find_and_remove_port(port_ranges, port) - rule.destination_port_ranges = new_ranges + if rule.destination_port_range is not None: + if int(rule.destination_port_range) == port: + security_rules.remove(rule) + else: + port_ranges = rule.destination_port_ranges + new_ranges = self._find_and_remove_port(port_ranges, port) + rule.destination_port_ranges = new_ranges network_security_group.security_rules = security_rules From f5d0266cde070b5c5196e9500a95258a945ddaa5 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni <73834811+kshrutik@users.noreply.github.com> Date: Mon, 5 Dec 2022 20:59:53 +0530 Subject: [PATCH 23/23] PLA-45823 - Updated remediation job to restrict unsecured HTTP requests for S3 Bucket (#131) --- .../aws_s3_bucket_policy_allow_https.py | 2 +- test/unit/test_aws_s3_bucket_policy_allow_https.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/remediation_worker/jobs/aws_s3_bucket_policy_allow_https/aws_s3_bucket_policy_allow_https.py b/remediation_worker/jobs/aws_s3_bucket_policy_allow_https/aws_s3_bucket_policy_allow_https.py index 4fe914a..e2d7663 100644 --- a/remediation_worker/jobs/aws_s3_bucket_policy_allow_https/aws_s3_bucket_policy_allow_https.py +++ b/remediation_worker/jobs/aws_s3_bucket_policy_allow_https/aws_s3_bucket_policy_allow_https.py @@ -103,7 +103,7 @@ def remediate(self, client, cloud_account_id, bucket_name): "Sid": "Restrict Non-https Requests", "Effect": "Deny", "Principal": "*", - "Action": "s3:GetObject", + "Action": "s3:*", "Resource": f"arn:aws:s3:::{bucket_name}/*", "Condition": {"Bool": {"aws:SecureTransport": "false"}}, } diff --git a/test/unit/test_aws_s3_bucket_policy_allow_https.py b/test/unit/test_aws_s3_bucket_policy_allow_https.py index b1604b9..53c467f 100644 --- a/test/unit/test_aws_s3_bucket_policy_allow_https.py +++ b/test/unit/test_aws_s3_bucket_policy_allow_https.py @@ -98,7 +98,7 @@ def test_remediate_success(self): "Sid": "Restrict Non-https Requests", "Effect": "Deny", "Principal": "*", - "Action": "s3:GetObject", + "Action": "s3:*", "Resource": "arn:aws:s3:::bucket_name/*", "Condition": {"Bool": {"aws:SecureTransport": "false"}}, },