From 51fbc004b097a3ead82db5eac40177e1c2816dce Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni Date: Thu, 29 Apr 2021 18:18:52 +0530 Subject: [PATCH] PLA-25428 - Remediation Job to set minimum password length --- .../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")