From 9f2df264b1c08c4ffe07b1c117dc80fd5bb0f0bc Mon Sep 17 00:00:00 2001 From: Mohammad Zuber Khan Date: Wed, 26 Aug 2020 12:41:48 -0700 Subject: [PATCH 1/5] PLA-16779: Add remediation job for azure security group port 22 --- .../README | 46 ++++ .../__init__.py | 0 ...re_network_security_group_close_port_22.py | 214 ++++++++++++++++++ .../constraints.txt | 109 +++++++++ .../cred_wrapper.py | 61 +++++ .../requirements-dev.txt | 6 + .../requirements.txt | 6 + ...re_network_security_group_close_port_22.py | 96 ++++++++ 8 files changed, 538 insertions(+) create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/README create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/__init__.py create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/requirements.txt create mode 100644 test/unit/test_azure_network_security_group_close_port_22.py diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/README b/remediation_worker/jobs/azure_network_security_group_close_port_22/README new file mode 100644 index 0000000..3b07d2e --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/README @@ -0,0 +1,46 @@ +# Close Port 22 for a Network Security Group + +This job blocks public access to port 22 + +## Getting Started + +### Prerequisites + +The provided Azure service principal must have permissions to make changes to the network security groups and security +rules. +Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-network-security-group#permissions): + + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r ../../requirements.txt + python3 azure_network_security_group_close_port_22.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 +``` + +## Contributing + +TODO: Add contributing.md +An example could be find here: +Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us. + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/your/project/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/azure_network_security_group_close_port_22/__init__.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/__init__.py new file mode 100644 index 0000000..e69de29 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 new file mode 100644 index 0000000..4014ebf --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py @@ -0,0 +1,214 @@ +# 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 json +import os +import sys +import logging + +# from cred_wrapper import CredentialWrapper +from azure.mgmt.network import NetworkManagementClient +from azure.common.credentials import ServicePrincipalCredentials + +logging.basicConfig(level=logging.INFO) + + +def logcall(f, *args, **kwargs): + logging.info( + "%s(%s)", + f.__name__, + ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), + ) + logging.info(f(*args, **kwargs)) + + +class NetworkSecurityGroupClosePort22(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: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + logging.info("unparsed params") + logging.info(f" {remediation_entry}") + + security_group_name = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectId" + ] + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" security_group: {security_group_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" region: {region}") + + return { + "security_group_name": security_group_name, + "resource_group_name": resource_group_name, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, security_group_name): + """Block public access to port 22 + + :param client: Instance of the Azure NetworkManagementClient. + :param resource_group_name: The name of the resource group to which the security_group belongs + :param security_group_name: The name of the security group. You must specify the + security group name in the request. + :type security_group_name: str. + :type resource_group_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + port = 22 + + network_security_group = client.network_security_groups.get( + resource_group_name=resource_group_name, + network_security_group_name=security_group_name, + ) + + security_rules = network_security_group.security_rules + + for rule in security_rules: + if ( + rule.access != "Allow" + or rule.direction != "Inbound" + or rule.source_address_prefix != "*" + ): + 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 + + network_security_group.security_rules = security_rules + + # Revoke permission for port 22 + logging.info("revoking permissions for port 22") + try: + logging.info( + " executing client.network_security_groups.create_or_update" + ) + 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( + resource_group_name, security_group_name, network_security_group + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def _find_and_remove_port(self, port_ranges, port): + """Remove the port from the port range. + + :param port_ranges: port ranges to be updated. + :param port: port to be removed + :type port_ranges: list. + :type port: int + :returns: list of port_ranges + """ + result = [] + for port_range in port_ranges: + if "-" in port_range: + boundaries = port_range.split("-") + if int(boundaries[0]) <= port and int(boundaries[1]) >= port: + if int(boundaries[0]) == port: + new_range_start = port + 1 + new_range_end = int(boundaries[1]) + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + elif int(boundaries[1]) == port: + new_range_start = int(boundaries[0]) + new_range_end = port - 1 + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + else: + range1_start = int(boundaries[0]) + range1_end = port - 1 + range2_start = port + 1 + range2_end = int(boundaries[1]) + + if range1_start != range1_end: + result.append(str(range1_start) + "-" + str(range1_end)) + else: + result.append(str(range1_start)) + + if range2_start != range2_end: + result.append(str(range2_start) + "-" + str(range2_end)) + else: + result.append(str(range2_start)) + else: + result.append(port_range) + elif int(port_range) != port: + result.append(port_range) + return result + + 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]) + + # credentials = CredentialWrapper() + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + + client = NetworkManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, params["resource_group_name"], params["security_group_name"] + ) + + +if __name__ == "__main__": + sys.exit(NetworkSecurityGroupClosePort22().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt b/remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt new file mode 100644 index 0000000..42600dd --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt @@ -0,0 +1,109 @@ +adal==1.2.4 \ + --hash=sha256:7a15d22b1ee7ce1be92441199958748982feba6b7dec35fbf60f9b607bad1bc0 \ + --hash=sha256:b332316f54d947f39acd9628e7d61d90f6e54d413d6f97025a51482c96bac6bc +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.0 \ + --hash=sha256:84bff2b05ce989942e7ca3a13237441fbd8ff6855aaf2979b2bc94b74a02be5f \ + --hash=sha256:c89bbdcdc13ad45fe57d775ed87b15baf6d0b039a1ecd0a1bc91d2f713cb1f08 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.2 \ + --hash=sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e \ + --hash=sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c \ + --hash=sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e \ + --hash=sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1 \ + --hash=sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4 \ + --hash=sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2 \ + --hash=sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c \ + --hash=sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0 \ + --hash=sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798 \ + --hash=sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1 \ + --hash=sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4 \ + --hash=sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731 \ + --hash=sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4 \ + --hash=sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c \ + --hash=sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487 \ + --hash=sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e \ + --hash=sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f \ + --hash=sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123 \ + --hash=sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c \ + --hash=sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b \ + --hash=sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650 \ + --hash=sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad \ + --hash=sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75 \ + --hash=sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82 \ + --hash=sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7 \ + --hash=sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15 \ + --hash=sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa \ + --hash=sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.0 \ + --hash=sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b \ + --hash=sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd \ + --hash=sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a \ + --hash=sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07 \ + --hash=sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71 \ + --hash=sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756 \ + --hash=sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559 \ + --hash=sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f \ + --hash=sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261 \ + --hash=sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053 \ + --hash=sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2 \ + --hash=sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f \ + --hash=sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b \ + --hash=sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77 \ + --hash=sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83 \ + --hash=sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f \ + --hash=sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67 \ + --hash=sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c \ + --hash=sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6 +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msal==1.4.3 \ + --hash=sha256:51b8e8e0d918d9b4813f006324e7c4e21eb76268dd4c1a06d811a3475ad4ac57 \ + --hash=sha256:82c0ca1103f4a040f3fa5325bfd6fb6c8273fbd1d6f7c1ea92bbc94fcc360c46 +msrest==0.6.18 \ + --hash=sha256:4993023011663b4273f15432fab75cc747dfa0bca1816d8122a7d1f9fdd9288d \ + --hash=sha256:5f4ef9b8cc207d93978b1a58f055179686b9f30a5e28041872db97a4a1c49b96 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +pyjwt==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py new file mode 100644 index 0000000..bcf137c --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py @@ -0,0 +1,61 @@ + +# Wrap credentials from azure-identity to be compatible with SDK that needs msrestazure or azure.common.credentials +# Need msrest >= 0.6.0 +# See also https://pypi.org/project/azure-identity/ + +from msrest.authentication import BasicTokenAuthentication +from azure.core.pipeline.policies import BearerTokenCredentialPolicy +from azure.core.pipeline import PipelineRequest, PipelineContext +from azure.core.pipeline.transport import HttpRequest + +from azure.identity import DefaultAzureCredential + +class CredentialWrapper(BasicTokenAuthentication): + def __init__(self, credential=None, resource_id="https://management.azure.com/.default", **kwargs): + """Wrap any azure-identity credential to work with SDK that needs azure.common.credentials/msrestazure. + + Default resource is ARM (syntax of endpoint v2) + + :param credential: Any azure-identity credential (DefaultAzureCredential by default) + :param str resource_id: The scope to use to get the token (default ARM) + """ + super(CredentialWrapper, self).__init__(None) + if credential is None: + credential = DefaultAzureCredential() + self._policy = BearerTokenCredentialPolicy(credential, resource_id, **kwargs) + + def _make_request(self): + return PipelineRequest( + HttpRequest( + "CredentialWrapper", + "https://fakeurl" + ), + PipelineContext(None) + ) + + def set_token(self): + """Ask the azure-core BearerTokenCredentialPolicy policy to get a token. + + Using the policy gives us for free the caching system of azure-core. + We could make this code simpler by using private method, but by definition + I can't assure they will be there forever, so mocking a fake call to the policy + to extract the token, using 100% public API.""" + request = self._make_request() + self._policy.on_request(request) + # Read Authorization, and get the second part after Bearer + token = request.http_request.headers["Authorization"].split(" ", 1)[1] + self.token = {"access_token": token} + + def signed_session(self, session=None): + self.set_token() + return super(CredentialWrapper, self).signed_session(session) + +if __name__ == "__main__": + import os + credentials = CredentialWrapper() + subscription_id = os.environ.get("AZURE_SUBSCRIPTION_ID", "") + + from azure.mgmt.resource import ResourceManagementClient + client = ResourceManagementClient(credentials, subscription_id) + for rg in client.resource_groups.list(): + print(rg.name) \ No newline at end of file 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 new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/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/azure_network_security_group_close_port_22/requirements.txt b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements.txt new file mode 100644 index 0000000..3c68ef7 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc diff --git a/test/unit/test_azure_network_security_group_close_port_22.py b/test/unit/test_azure_network_security_group_close_port_22.py new file mode 100644 index 0000000..0c6e394 --- /dev/null +++ b/test/unit/test_azure_network_security_group_close_port_22.py @@ -0,0 +1,96 @@ +# 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 azure.mgmt.network.models import NetworkSecurityGroup +from azure.mgmt.network.models import SecurityRule +from mock import Mock + +from remediation_worker.jobs.azure_network_security_group_close_port_22.azure_network_security_group_close_port_22 import ( + NetworkSecurityGroupClosePort22, +) + + +@pytest.fixture +def valid_payload1(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5d903dcc90f363146b6", + "Service": "Network", + "FindingInfo": { + "FindingId": "e1606076-d55c-42c5-9ca7-93e933b1e672", + "ObjectId": "security_group_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestNetworkSecurityGroupClosePort22(object): + def test_parse_payload(self, valid_payload1): + params = NetworkSecurityGroupClosePort22().parse(valid_payload1) + assert params["security_group_name"] == "security_group_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + client.network_security_groups.get.return_value = NetworkSecurityGroup( + id="nsg", + security_rules=[ + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["22", "3389"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["20-30", "3389"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_range="22", + ), + ], + ) + + action = NetworkSecurityGroupClosePort22() + assert action.remediate(client, "security_group_name", "resource_group") == 0 + assert client.network_security_groups.create_or_update.call_count == 1 + + call_args = client.network_security_groups.create_or_update.call_args + updated_sg = call_args.args[2] + security_rules = updated_sg.security_rules + assert len(security_rules) == 2 + assert security_rules[0].destination_port_ranges == ["3389"] + assert security_rules[1].destination_port_ranges == ["20-21", "23-30", "3389"] + + def test_remediate_with_exception(self): + client = Mock() + client.network_security_groups.create_or_update.side_effect = Exception + action = NetworkSecurityGroupClosePort22() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") From 6d55e6fa7569f5ad7c2847d87abeabf9f72dcfa9 Mon Sep 17 00:00:00 2001 From: Mohammad Zuber Khan Date: Wed, 9 Sep 2020 14:18:45 -0700 Subject: [PATCH 2/5] add test dependencies --- ...re_network_security_group_close_port_22.py | 5 -- .../cred_wrapper.py | 61 ------------------- .../requirements-dev.txt | 27 ++++++++ tox.ini | 7 +++ 4 files changed, 34 insertions(+), 66 deletions(-) delete mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py 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 4014ebf..9ebf28a 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 @@ -17,7 +17,6 @@ import sys import logging -# from cred_wrapper import CredentialWrapper from azure.mgmt.network import NetworkManagementClient from azure.common.credentials import ServicePrincipalCredentials @@ -44,14 +43,11 @@ def parse(self, payload): :raises: KeyError, JSONDecodeError """ remediation_entry = json.loads(payload) - logging.info("unparsed params") - logging.info(f" {remediation_entry}") security_group_name = remediation_entry["notificationInfo"]["FindingInfo"][ "ObjectId" ] region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] - object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ "ObjectChain" ] @@ -197,7 +193,6 @@ def run(self, args): """ params = self.parse(args[1]) - # credentials = CredentialWrapper() credentials = ServicePrincipalCredentials( client_id=os.environ.get("AZURE_CLIENT_ID"), secret=os.environ.get("AZURE_CLIENT_SECRET"), diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py deleted file mode 100644 index bcf137c..0000000 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/cred_wrapper.py +++ /dev/null @@ -1,61 +0,0 @@ - -# Wrap credentials from azure-identity to be compatible with SDK that needs msrestazure or azure.common.credentials -# Need msrest >= 0.6.0 -# See also https://pypi.org/project/azure-identity/ - -from msrest.authentication import BasicTokenAuthentication -from azure.core.pipeline.policies import BearerTokenCredentialPolicy -from azure.core.pipeline import PipelineRequest, PipelineContext -from azure.core.pipeline.transport import HttpRequest - -from azure.identity import DefaultAzureCredential - -class CredentialWrapper(BasicTokenAuthentication): - def __init__(self, credential=None, resource_id="https://management.azure.com/.default", **kwargs): - """Wrap any azure-identity credential to work with SDK that needs azure.common.credentials/msrestazure. - - Default resource is ARM (syntax of endpoint v2) - - :param credential: Any azure-identity credential (DefaultAzureCredential by default) - :param str resource_id: The scope to use to get the token (default ARM) - """ - super(CredentialWrapper, self).__init__(None) - if credential is None: - credential = DefaultAzureCredential() - self._policy = BearerTokenCredentialPolicy(credential, resource_id, **kwargs) - - def _make_request(self): - return PipelineRequest( - HttpRequest( - "CredentialWrapper", - "https://fakeurl" - ), - PipelineContext(None) - ) - - def set_token(self): - """Ask the azure-core BearerTokenCredentialPolicy policy to get a token. - - Using the policy gives us for free the caching system of azure-core. - We could make this code simpler by using private method, but by definition - I can't assure they will be there forever, so mocking a fake call to the policy - to extract the token, using 100% public API.""" - request = self._make_request() - self._policy.on_request(request) - # Read Authorization, and get the second part after Bearer - token = request.http_request.headers["Authorization"].split(" ", 1)[1] - self.token = {"access_token": token} - - def signed_session(self, session=None): - self.set_token() - return super(CredentialWrapper, self).signed_session(session) - -if __name__ == "__main__": - import os - credentials = CredentialWrapper() - subscription_id = os.environ.get("AZURE_SUBSCRIPTION_ID", "") - - from azure.mgmt.resource import ResourceManagementClient - client = ResourceManagementClient(credentials, subscription_id) - for rg in client.resource_groups.list(): - print(rg.name) \ No newline at end of file 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 9412e93..1143bce 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 @@ -1,6 +1,33 @@ -r requirements.txt -c constraints.txt +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b pytest==6.0.1 \ --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 diff --git a/tox.ini b/tox.ini index bc2dd70..6c3c30d 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ envlist = unit-security-group-close-port-3389 unit-rds-backup-retention-30-days unit-security-group-close-port-22 + azure_network_security_group_close_port_22 [testenv] passenv = @@ -90,3 +91,9 @@ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_security_group_close_port_22.py deps = -r remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt + +[testenv:unit-azure-network-security-group-close-port-22] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_network_security_group_close_port_22.py +deps = -r remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt From 5990325952dbc9017980ffa369d4e8c4a1208e73 Mon Sep 17 00:00:00 2001 From: Mohammad Zuber Khan Date: Thu, 10 Sep 2020 13:42:41 -0700 Subject: [PATCH 3/5] update README.md --- .../{README => README.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename remediation_worker/jobs/azure_network_security_group_close_port_22/{README => README.md} (68%) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/README b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md similarity index 68% rename from remediation_worker/jobs/azure_network_security_group_close_port_22/README rename to remediation_worker/jobs/azure_network_security_group_close_port_22/README.md index 3b07d2e..705bd8f 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/README +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md @@ -26,10 +26,10 @@ You may run test using following command under vss-remediation-worker-job-code-p ``` ## 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. -TODO: Add contributing.md -An example could be find here: -Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us. +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). ## Versioning From db364dd31f01a04b5948203a1d64bbbd4641d3dc Mon Sep 17 00:00:00 2001 From: Mohammad Zuber Khan Date: Sun, 13 Sep 2020 12:53:12 -0700 Subject: [PATCH 4/5] add deployment info to the README --- .../README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md index 705bd8f..78cd4d6 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md @@ -15,15 +15,30 @@ Details for the permissions can be found [here](https://docs.microsoft.com/en-us You may run this script using following commands: ```shell script - pip install -r ../../requirements.txt + pip install -r requirements.txt python3 azure_network_security_group_close_port_22.py ``` ## Running the tests You may run test using following command under vss-remediation-worker-job-code-python directory: ```shell script + pip install -r requirements-dev.txt 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). From 5fe2c54ace989e277ff9ef83ff2ec49358705cc9 Mon Sep 17 00:00:00 2001 From: Mohammad Zuber Khan Date: Mon, 14 Sep 2020 12:29:22 -0700 Subject: [PATCH 5/5] add rule information --- .../README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md index 78cd4d6..b0a7a35 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md @@ -1,6 +1,14 @@ # Close Port 22 for a Network Security Group -This job blocks public access to port 22 +This job blocks public access to port 22. + +### Applicable Rule + +#### Rule ID: +5c8c26847a550e1fb6560cab + +#### Rule Name: +The security group allows access to SSH port (22) ## Getting Started @@ -41,7 +49,7 @@ SSH into the EC2 instance and run the command below to deploy the worker image: ## 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). +The Secure State team 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).