diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 0f2e7c4ff..464893986 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -2481,6 +2481,70 @@ - [ ] update_workflow +## guardduty +
+3% implemented + +- [ ] accept_invitation +- [ ] archive_findings +- [X] create_detector +- [ ] create_filter +- [ ] create_ip_set +- [ ] create_members +- [ ] create_publishing_destination +- [ ] create_sample_findings +- [ ] create_threat_intel_set +- [ ] decline_invitations +- [ ] delete_detector +- [ ] delete_filter +- [ ] delete_invitations +- [ ] delete_ip_set +- [ ] delete_members +- [ ] delete_publishing_destination +- [ ] delete_threat_intel_set +- [ ] describe_organization_configuration +- [ ] describe_publishing_destination +- [ ] disable_organization_admin_account +- [ ] disassociate_from_master_account +- [ ] disassociate_members +- [ ] enable_organization_admin_account +- [ ] get_detector +- [ ] get_filter +- [ ] get_findings +- [ ] get_findings_statistics +- [ ] get_invitations_count +- [ ] get_ip_set +- [ ] get_master_account +- [ ] get_member_detectors +- [ ] get_members +- [ ] get_threat_intel_set +- [ ] get_usage_statistics +- [ ] invite_members +- [X] list_detectors +- [ ] list_filters +- [ ] list_findings +- [ ] list_invitations +- [ ] list_ip_sets +- [ ] list_members +- [ ] list_organization_admin_accounts +- [ ] list_publishing_destinations +- [ ] list_tags_for_resource +- [ ] list_threat_intel_sets +- [ ] start_monitoring_members +- [ ] stop_monitoring_members +- [ ] tag_resource +- [ ] unarchive_findings +- [ ] untag_resource +- [ ] update_detector +- [ ] update_filter +- [ ] update_findings_feedback +- [ ] update_ip_set +- [ ] update_member_detectors +- [ ] update_organization_configuration +- [ ] update_publishing_destination +- [ ] update_threat_intel_set +
+ ## iam
67% implemented @@ -4945,7 +5009,6 @@ - greengrass - greengrassv2 - groundstation -- guardduty - health - healthlake - honeycode diff --git a/docs/docs/services/guardduty.rst b/docs/docs/services/guardduty.rst new file mode 100644 index 000000000..9189ce47a --- /dev/null +++ b/docs/docs/services/guardduty.rst @@ -0,0 +1,90 @@ +.. _implementedservice_guardduty: + +.. |start-h3| raw:: html + +

+ +.. |end-h3| raw:: html + +

+ +========= +guardduty +========= + +|start-h3| Example usage |end-h3| + +.. sourcecode:: python + + @mock_guardduty + def test_guardduty_behaviour: + boto3.client("guardduty") + ... + + + +|start-h3| Implemented features for this service |end-h3| + +- [ ] accept_invitation +- [ ] archive_findings +- [X] create_detector +- [ ] create_filter +- [ ] create_ip_set +- [ ] create_members +- [ ] create_publishing_destination +- [ ] create_sample_findings +- [ ] create_threat_intel_set +- [ ] decline_invitations +- [ ] delete_detector +- [ ] delete_filter +- [ ] delete_invitations +- [ ] delete_ip_set +- [ ] delete_members +- [ ] delete_publishing_destination +- [ ] delete_threat_intel_set +- [ ] describe_organization_configuration +- [ ] describe_publishing_destination +- [ ] disable_organization_admin_account +- [ ] disassociate_from_master_account +- [ ] disassociate_members +- [ ] enable_organization_admin_account +- [ ] get_detector +- [ ] get_filter +- [ ] get_findings +- [ ] get_findings_statistics +- [ ] get_invitations_count +- [ ] get_ip_set +- [ ] get_master_account +- [ ] get_member_detectors +- [ ] get_members +- [ ] get_threat_intel_set +- [ ] get_usage_statistics +- [ ] invite_members +- [X] list_detectors + + The MaxResults and NextToken-parameter have not yet been implemented. + + +- [ ] list_filters +- [ ] list_findings +- [ ] list_invitations +- [ ] list_ip_sets +- [ ] list_members +- [ ] list_organization_admin_accounts +- [ ] list_publishing_destinations +- [ ] list_tags_for_resource +- [ ] list_threat_intel_sets +- [ ] start_monitoring_members +- [ ] stop_monitoring_members +- [ ] tag_resource +- [ ] unarchive_findings +- [ ] untag_resource +- [ ] update_detector +- [ ] update_filter +- [ ] update_findings_feedback +- [ ] update_ip_set +- [ ] update_member_detectors +- [ ] update_organization_configuration +- [ ] update_publishing_destination +- [ ] update_threat_intel_set + diff --git a/moto/__init__.py b/moto/__init__.py index b5879e4c0..f7191f1ea 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -95,6 +95,7 @@ mock_forecast = lazy_load(".forecast", "mock_forecast") mock_glacier = lazy_load(".glacier", "mock_glacier") mock_glacier_deprecated = lazy_load(".glacier", "mock_glacier_deprecated") mock_glue = lazy_load(".glue", "mock_glue") +mock_guardduty = lazy_load(".guardduty", "mock_guardduty") mock_iam = lazy_load(".iam", "mock_iam") mock_iam_deprecated = lazy_load(".iam", "mock_iam_deprecated") mock_iot = lazy_load(".iot", "mock_iot") @@ -198,7 +199,6 @@ class MockAll(ContextDecorator): mock_all = MockAll - # import logging # logging.getLogger('boto').setLevel(logging.CRITICAL) diff --git a/moto/backend_index.py b/moto/backend_index.py index b85b7e3a2..3fed47bfb 100644 --- a/moto/backend_index.py +++ b/moto/backend_index.py @@ -66,6 +66,7 @@ backend_url_patterns = [ ("forecast", re.compile("https?://forecast\\.(.+)\\.amazonaws\\.com")), ("glacier", re.compile("https?://glacier\\.(.+)\\.amazonaws.com")), ("glue", re.compile("https?://glue\\.(.+)\\.amazonaws\\.com")), + ("guardduty", re.compile("https?://guardduty\\.(.+)\\.amazonaws\\.com")), ("iam", re.compile("https?://iam\\.(.*\\.)?amazonaws\\.com")), ("iot", re.compile("https?://iot\\.(.+)\\.amazonaws\\.com")), ("iot-data", re.compile("https?://data\\.iot\\.(.+)\\.amazonaws.com")), diff --git a/moto/guardduty/__init__.py b/moto/guardduty/__init__.py new file mode 100644 index 000000000..68e09dedd --- /dev/null +++ b/moto/guardduty/__init__.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from .models import guardduty_backends +from ..core.models import base_decorator + +guardduty_backend = guardduty_backends["us-east-1"] +mock_guardduty = base_decorator(guardduty_backends) diff --git a/moto/guardduty/models.py b/moto/guardduty/models.py new file mode 100644 index 000000000..38518e313 --- /dev/null +++ b/moto/guardduty/models.py @@ -0,0 +1,79 @@ +from __future__ import unicode_literals +from boto3 import Session +from moto.core import BaseBackend, BaseModel +from datetime import datetime +from uuid import uuid4 + + +class GuardDutyBackend(BaseBackend): + def __init__(self, region_name=None): + super(GuardDutyBackend, self).__init__() + self.region_name = region_name + self.detectors = {} + + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + def create_detector( + self, enable, client_token, finding_publishing_frequency, data_sources, tags + ): + if finding_publishing_frequency not in [ + "FIFTEEN_MINUTES", + "ONE_HOUR", + "SIX_HOURS", + ]: + finding_publishing_frequency = "SIX_HOURS" + + service_role = "AWSServiceRoleForAmazonGuardDuty" + detector = Detector( + self, + datetime.now, + finding_publishing_frequency, + service_role, + enable, + data_sources, + tags, + ) + self.detectors[detector.id] = detector + return detector.id + + def list_detectors(self): + """ + The MaxResults and NextToken-parameter have not yet been implemented. + """ + detectorids = [] + for detector in self.detectors: + detectorids.append(self.detectors[detector].id) + return detectorids + + +class Detector(BaseModel): + def __init__( + self, + created_at, + finding_publish_freq, + service_role, + status, + updated_at, + datasources, + tags, + ): + self.id = str(uuid4()) + self.created_at = created_at + self.finding_publish_freq = finding_publish_freq + self.service_role = service_role + self.status = status + self.updated_at = updated_at + self.datasources = datasources + self.tags = tags + + +guardduty_backends = {} +for region in Session().get_available_regions("guardduty"): + guardduty_backends[region] = GuardDutyBackend() +for region in Session().get_available_regions("guardduty", partition_name="aws-us-gov"): + guardduty_backends[region] = GuardDutyBackend() +for region in Session().get_available_regions("guardduty", partition_name="aws-cn"): + guardduty_backends[region] = GuardDutyBackend() diff --git a/moto/guardduty/responses.py b/moto/guardduty/responses.py new file mode 100644 index 000000000..77d55c384 --- /dev/null +++ b/moto/guardduty/responses.py @@ -0,0 +1,39 @@ +from __future__ import unicode_literals +from moto.core.responses import BaseResponse +from .models import guardduty_backends +import json + + +class GuardDutyResponse(BaseResponse): + SERVICE_NAME = "guardduty" + + @property + def guardduty_backend(self): + return guardduty_backends[self.region] + + def detector(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "POST": + return self.create_detector() + elif request.method == "GET": + return self.list_detectors() + else: + return 404, {}, "" + + def create_detector(self): + enable = self._get_param("enable") + client_token = self._get_param("clientToken") + finding_publishing_frequency = self._get_param("findingPublishingFrequency") + data_sources = self._get_param("dataSources") + tags = self._get_param("tags") + + detector_id = self.guardduty_backend.create_detector( + enable, client_token, finding_publishing_frequency, data_sources, tags + ) + + return 200, {}, json.dumps(dict(detectorId=detector_id)) + + def list_detectors(self): + detector_ids = self.guardduty_backend.list_detectors() + + return 200, {}, json.dumps({"detectorIds": detector_ids}) diff --git a/moto/guardduty/urls.py b/moto/guardduty/urls.py new file mode 100644 index 000000000..6012d35c9 --- /dev/null +++ b/moto/guardduty/urls.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from .responses import GuardDutyResponse + +response = GuardDutyResponse() + +url_bases = [ + "https?://guardduty\\.(.+)\\.amazonaws\\.com", +] + + +url_paths = { + "{0}/detector$": response.detector, +} diff --git a/tests/test_guardduty/__init__.py b/tests/test_guardduty/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_guardduty/test_guardduty.py b/tests/test_guardduty/test_guardduty.py new file mode 100644 index 000000000..72ff4c9fb --- /dev/null +++ b/tests/test_guardduty/test_guardduty.py @@ -0,0 +1,51 @@ +import boto3 +import sure # noqa # pylint: disable=unused-import + +from moto import mock_guardduty + + +@mock_guardduty +def test_create_detector(): + client = boto3.client("guardduty", region_name="us-east-1") + response = client.create_detector( + Enable=True, + ClientToken="745645734574758463758", + FindingPublishingFrequency="ONE_HOUR", + DataSources={"S3Logs": {"Enable": True}}, + Tags={}, + ) + response.should.have.key("DetectorId") + response["DetectorId"].shouldnt.equal(None) + + +@mock_guardduty +def test_create_detector_with_minimal_params(): + client = boto3.client("guardduty", region_name="us-east-1") + response = client.create_detector(Enable=True) + response.should.have.key("DetectorId") + response["DetectorId"].shouldnt.equal(None) + + +@mock_guardduty +def test_list_detectors_initial(): + client = boto3.client("guardduty", region_name="us-east-1") + + response = client.list_detectors() + response.should.have.key("DetectorIds").equals([]) + + +@mock_guardduty +def test_list_detectors(): + client = boto3.client("guardduty", region_name="us-east-1") + d1 = client.create_detector( + Enable=True, + ClientToken="745645734574758463758", + FindingPublishingFrequency="ONE_HOUR", + DataSources={"S3Logs": {"Enable": True}}, + Tags={}, + )["DetectorId"] + d2 = client.create_detector(Enable=False,)["DetectorId"] + + response = client.list_detectors() + response.should.have.key("DetectorIds") + set(response["DetectorIds"]).should.equal({d1, d2}) diff --git a/tests/test_guardduty/test_server.py b/tests/test_guardduty/test_server.py new file mode 100644 index 000000000..4a09532fa --- /dev/null +++ b/tests/test_guardduty/test_server.py @@ -0,0 +1,14 @@ +import json +import sure # noqa # pylint: disable=unused-import + +import moto.server as server + + +def test_create_without_enable_option(): + backend = server.create_backend_app("guardduty") + test_client = backend.test_client() + + body = {"enable": "True"} + response = test_client.post("/detector", data=json.dumps(body)) + response.status_code.should.equal(200) + json.loads(response.data).should.have.key("detectorId")