Merge pull request #2188 from guardicore/2165-labda-decoupling

2165 lambda decoupling
This commit is contained in:
Mike Salvatore 2022-08-12 10:33:51 -04:00 committed by GitHub
commit 82c7782ff0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 105 additions and 27 deletions

View File

@ -40,6 +40,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- The "/api/netmap/nodeStates" endpoint to "/api/netmap/node-states". #1888
- All "/api/monkey_control" endpoints to "/api/monkey-control". #1888
- All "/api/monkey" endpoints to "/api/agent". #1888
- Analytics and version update queries are sent separately instead of just one query. #2165
- Update MongoDB version to 4.4.x. #1924
- Endpoint to get agent binaries from "/api/agent/download/<string:os>" to
"/api/agent-binaries/<string:os>". #1978

View File

@ -81,7 +81,6 @@ Monkey in the newly created folder.
## Reset the Monkey Island password
{{% notice warning %}}
If you reset the credentials, the database will be cleared. Any findings of the Infection Monkey from previous runs will be lost. <br/><br/>
However, you can save the Monkey's existing configuration by logging in with your current credentials and clicking on the **Export config** button on the configuration page.
@ -160,8 +159,25 @@ If internet access is available, the Infection Monkey will use the internet for
The Monkey performs queries out to the Internet on two separate occasions:
1. The Infection Monkey agent checks if it has internet access by performing requests to pre-configured domains. By default, these domains are `monkey.guardicore.com` and `www.google.com`, which can be changed. The request doesn't include any extra information - it's a GET request with no extra parameters. Since the Infection Monkey is 100% open-source, you can find the domains in the configuration [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152) and the code that performs the internet check [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123). This **IS NOT** used for statistics collection.
1. After installing the Monkey Island, it sends a request to check for updates on `updates.infectionmonkey.com`. The request doesn't include any PII other than the IP address of the request. It also includes the server's deployment type (e.g., Windows Server, AppImage, Docker) and the server's version (e.g., "1.6.3"), so we can check if we have an update available for this type of deployment. Since the Infection Monkey is 100% open-source, you can inspect the code that performs this [here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/monkey_island/cc/services/version_update.py#L37). This **IS** used for statistics collection. However, due to this data's anonymous nature, we use this to get an aggregate assumption of how many deployments we see over a specific time period - it's not used for "personal" tracking.
1. The Infection Monkey agent checks if it has internet access by performing
requests to pre-configured domains. By default, these domains are
`monkey.guardicore.com` and `www.google.com`, which can be changed. The
request doesn't include any extra information - it's a GET request with no
extra parameters. Since the Infection Monkey is 100% open-source, you can
find the domains in the configuration
[here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/config.py#L152)
and the code that performs the internet check
[here](https://github.com/guardicore/monkey/blob/85c70a3e7125217c45c751d89205e95985b279eb/monkey/infection_monkey/network/info.py#L123).
This **IS NOT** used for statistics collection.
1. After the Monkey Island starts it sends a GET request with current
deployment type to the update server to fetch the latest version and a
download link for it. This information is used by the Monkey Island to
suggest an update if one is available. No information gets collected during
this process.
1. After the Monkey Island starts it sends a GET request to the analytics
server with your deployment type and a version number. This information gets
collected on the analytics server. It is used to understand which deployment
types/versions are no longer used and can be deprecated.
## Logging and how to find logs

View File

@ -5,8 +5,11 @@ import sys
from pathlib import Path
import gevent.hub
import requests
from gevent.pywsgi import WSGIServer
from monkey_island.cc import Version
from monkey_island.cc.deployment import Deployment
from monkey_island.cc.server_utils.consts import ISLAND_PORT
from monkey_island.cc.setup.config_setup import get_server_config
@ -150,6 +153,7 @@ def _start_island_server(
error_log=logger,
)
_log_init_info()
_send_analytics(container)
http_server.serve_forever()
@ -173,3 +177,25 @@ def _log_web_interface_access_urls():
"To access the web interface, navigate to one of the the following URLs using your "
f"browser: {web_interface_urls}"
)
ANALYTICS_URL = (
"https://m15mjynko3.execute-api.us-east-1.amazonaws.com/default?version={"
"version}&deployment={deployment}"
)
def _send_analytics(di_container):
version = di_container.resolve(Version)
deployment = di_container.resolve(Deployment)
url = ANALYTICS_URL.format(deployment=deployment.value, version=version.version_number)
try:
response = requests.get(url).json()
logger.info(
f"Version number and deployment type was sent to analytics server. "
f"The response is: {response}"
)
except requests.exceptions.ConnectionError as err:
logger.info(
f"Failed to send deployment type and version number to the analytics server: {err}"
)

View File

@ -1,15 +1,13 @@
import logging
from threading import Event, Thread
from typing import Optional, Tuple
import requests
import semantic_version
from .deployment import Deployment
VERSION_SERVER_URL_PREF = "https://updates.infectionmonkey.com"
VERSION_SERVER_CHECK_NEW_URL = VERSION_SERVER_URL_PREF + "?deployment=%s&monkey_version=%s"
VERSION_SERVER_DOWNLOAD_URL = VERSION_SERVER_CHECK_NEW_URL + "&is_download=true"
# TODO get redirects instead of using direct links to AWS
LATEST_VERSION_URL = "https://njf01cuupf.execute-api.us-east-1.amazonaws.com/default?deployment={}"
LATEST_VERSION_TIMEOUT = 7
logger = logging.getLogger(__name__)
@ -55,29 +53,23 @@ class Version:
return self._download_url
def _set_version_metadata(self):
self._latest_version = self._get_latest_version()
self._download_url = self._get_download_link()
self._latest_version, self._download_url = self._get_version_info()
self._initialization_complete.set()
def _get_latest_version(self) -> str:
url = VERSION_SERVER_CHECK_NEW_URL % (self._deployment.value, self._version_number)
def _get_version_info(self) -> Tuple[str, Optional[str]]:
url = LATEST_VERSION_URL.format(self._deployment.value)
try:
reply = requests.get(url, timeout=LATEST_VERSION_TIMEOUT)
response = requests.get(url, timeout=LATEST_VERSION_TIMEOUT).json()
except requests.exceptions.RequestException as err:
logger.warning(f"Failed to connect to {VERSION_SERVER_URL_PREF}: {err}")
return self._version_number
logger.warning(f"Failed to fetch version information from {url}: {err}")
return self._version_number, None
res = reply.json().get("newer_version", None)
try:
download_link = response["download_link"]
latest_version = response["version"]
except KeyError:
logger.error(f"Failed to fetch version information from {url}: {response}")
return self._version_number, None
if res is False:
return self._version_number
if not semantic_version.validate(res):
logger.warning(f"Recieved invalid version {res} from {VERSION_SERVER_URL_PREF}")
return self._version_number
return res.strip()
def _get_download_link(self):
return VERSION_SERVER_DOWNLOAD_URL % (self._deployment.value, self._version_number)
return latest_version, download_link

View File

@ -0,0 +1,43 @@
from unittest.mock import MagicMock
import pytest
import requests
from monkey_island.cc import Version
from monkey_island.cc.deployment import Deployment
failed_response = MagicMock()
failed_response.return_value.json.return_value = {"message": "Internal server error"}
successful_response = MagicMock()
SUCCESS_VERSION = "1.1.1"
SUCCESS_URL = "http://be_free.gov"
successful_response.return_value.json.return_value = {
"version": SUCCESS_VERSION,
"download_link": SUCCESS_URL,
}
@pytest.mark.parametrize(
"request_mock",
[
failed_response,
MagicMock(side_effect=requests.exceptions.RequestException("Timeout or something")),
],
)
def test_version__request_failed(monkeypatch, request_mock):
monkeypatch.setattr("requests.get", request_mock)
version = Version(version_number="1.0.0", deployment=Deployment.DEVELOP)
assert version.latest_version == "1.0.0"
assert version.download_url is None
def test_version__request_successful(monkeypatch):
monkeypatch.setattr("requests.get", successful_response)
version = Version(version_number="1.0.0", deployment=Deployment.DEVELOP)
assert version.latest_version == SUCCESS_VERSION
assert version.download_url == SUCCESS_URL