Merge pull request #2006 from guardicore/1968-global-agent-config

1968 global agent config
This commit is contained in:
Mike Salvatore 2022-06-09 12:15:55 -04:00 committed by GitHub
commit 6f090a4de0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 54 additions and 130 deletions

View File

@ -41,7 +41,6 @@ class ControlClient(object):
"ip_addresses": local_ips(),
"networks": get_host_subnets(),
"description": " ".join(platform.uname()),
"config": WormConfiguration.as_dict(),
"parent": parent,
"launch_time": agent_process.get_start_time(),
}
@ -50,7 +49,7 @@ class ControlClient(object):
monkey["tunnel"] = ControlClient.proxies.get("https")
requests.post( # noqa: DUO123
"https://%s/api/agent" % (WormConfiguration.current_server,),
f"https://{WormConfiguration.current_server}/api/agent",
data=json.dumps(monkey),
headers={"content-type": "application/json"},
verify=False,
@ -173,7 +172,7 @@ class ControlClient(object):
return
try:
reply = requests.get( # noqa: DUO123
"https://%s/api/agent/%s/legacy" % (WormConfiguration.current_server, GUID),
f"https://{WormConfiguration.current_server}/api/agent/",
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
@ -210,7 +209,7 @@ class ControlClient(object):
return
try:
requests.patch( # noqa: DUO123
"https://%s/api/agent/%s" % (WormConfiguration.current_server, GUID),
f"https://{WormConfiguration.current_server}/api/agent/{GUID}",
data=json.dumps({"config_error": True}),
headers={"content-type": "application/json"},
verify=False,

View File

@ -49,7 +49,7 @@ class ControlChannel(IControlChannel):
def get_config(self) -> dict:
try:
response = requests.get( # noqa: DUO123
f"https://{self._control_channel_server}/api/agent/{self._agent_id}",
f"https://{self._control_channel_server}/api/agent",
verify=False,
proxies=ControlClient.proxies,
timeout=SHORT_REQUEST_TIMEOUT,
@ -68,7 +68,7 @@ class ControlChannel(IControlChannel):
def get_credentials_for_propagation(self) -> PropagationCredentials:
propagation_credentials_url = (
f"https://{self._control_channel_server}/api/propagation-credentials/{self._agent_id}"
f"https://{self._control_channel_server}/api/propagation-credentials"
)
try:
response = requests.get( # noqa: DUO123

View File

@ -37,7 +37,6 @@ class Monkey(Document):
# SCHEMA
guid = StringField(required=True)
config = EmbeddedDocumentField("Config")
should_stop = BooleanField()
dead = BooleanField()
description = StringField()

View File

@ -15,7 +15,7 @@ class IslandConfiguration(AbstractResource):
def get(self):
return jsonify(
schema=ConfigService.get_config_schema(),
configuration=ConfigService.get_config(False, True, True),
configuration=ConfigService.get_config(True, True),
)
@jwt_required

View File

@ -21,28 +21,11 @@ class Monkey(AbstractResource):
urls = [
"/api/agent",
"/api/agent/<string:guid>",
# API Spec: Resource names should alternate with IDs (/api/agents/123/config-format/xyz)
"/api/agent/<string:guid>/<string:config_format>",
]
# Used by monkey. can't secure.
def get(self, guid=None, config_format=None, **kw):
if not guid:
guid = request.args.get("guid")
if guid:
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
# TODO: When the "legacy" format is no longer needed, update this logic and remove the
# "/api/agent/<string:guid>/<string:config_format>" route. Also considering not
# flattening the config in the first place.
if config_format == "legacy":
ConfigService.decrypt_flat_config(monkey_json["config"])
else:
ConfigService.format_flat_config_for_agent(monkey_json["config"])
return monkey_json
return {}
def get(self):
return {"config": ConfigService.format_flat_config_for_agent()}
# Used by monkey. can't secure.
@TestTelemStore.store_exported_telem
@ -54,8 +37,6 @@ class Monkey(AbstractResource):
monkey_json = json.loads(request.data)
update = {"$set": {"modifytime": datetime.now()}}
monkey = NodeService.get_monkey_by_guid(guid)
if "config" in monkey_json:
update["$set"]["config"] = monkey_json["config"]
if "config_error" in monkey_json:
update["$set"]["config_error"] = monkey_json["config_error"]
@ -84,16 +65,9 @@ class Monkey(AbstractResource):
monkey_json["modifytime"] = datetime.now()
ConfigService.save_initial_config_if_needed()
# if new monkey telem, change config according to "new monkeys" config.
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
# Update monkey configuration
new_config = ConfigService.get_flat_config(False, False)
monkey_json["config"] = monkey_json.get("config", {})
monkey_json["config"].update(new_config)
# try to find new monkey parent
parent = monkey_json.get("parent")
parent_to_add = (monkey_json.get("guid"), None) # default values in case of manual run

View File

@ -1,17 +1,15 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.resources.AbstractResource import AbstractResource
from monkey_island.cc.services.config import ConfigService
class PropagationCredentials(AbstractResource):
urls = ["/api/propagation-credentials/<string:guid>"]
urls = ["/api/propagation-credentials"]
def get(self, guid: str):
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
ConfigService.decrypt_flat_config(monkey_json["config"])
def get(self):
config = ConfigService.get_flat_config(should_decrypt=True)
propagation_credentials = ConfigService.get_config_propagation_credentials_from_flat_config(
monkey_json["config"]
config
)
return {"propagation_credentials": propagation_credentials}

View File

@ -60,12 +60,10 @@ class ConfigService:
pass
@staticmethod
def get_config(is_initial_config=False, should_decrypt=True, is_island=False):
def get_config(should_decrypt=True, is_island=False):
"""
Gets the entire global config.
:param is_initial_config: If True, the initial config will be returned instead of the \
current config. \
:param should_decrypt: If True, all config values which are set as encrypted will be \
decrypted. \
:param is_island: If True, will include island specific configuration parameters. \
@ -74,12 +72,8 @@ class ConfigService:
# is_initial_config and should_decrypt are only there to compare if we are on the
# default configuration or did user modified it already
config = (
mongo.db.config.find_one({"name": "initial" if is_initial_config else "newconfig"})
or {}
)
for field in ("name", "_id"):
config.pop(field, None)
config = mongo.db.config.find_one() or {}
config.pop("_id", None)
if should_decrypt and len(config) > 0:
ConfigService.decrypt_config(config)
if not is_island:
@ -87,14 +81,12 @@ class ConfigService:
return config
@staticmethod
def get_config_value(config_key_as_arr, is_initial_config=False, should_decrypt=True):
def get_config_value(config_key_as_arr, should_decrypt=True):
"""
Get a specific config value.
:param config_key_as_arr: The config key as an array.
e.g. ['basic', 'credentials','exploit_password_list'].
:param is_initial_config: If True, returns the value of the
initial config instead of the current config.
:param should_decrypt: If True, the value of the config key will be decrypted
(if it's in the list of encrypted config values).
:return: The value of the requested config key.
@ -102,9 +94,7 @@ class ConfigService:
config_key = functools.reduce(lambda x, y: x + "." + y, config_key_as_arr)
# This should just call get_config from repository. If None, then call get_default prob
config = mongo.db.config.find_one(
{"name": "initial" if is_initial_config else "newconfig"}, {config_key: 1}
)
config = mongo.db.config.find_one({}, {config_key: 1})
for config_key_part in config_key_as_arr:
config = config[config_key_part]
@ -124,11 +114,11 @@ class ConfigService:
@staticmethod
def set_config_value(config_key_as_arr, value):
mongo_key = ".".join(config_key_as_arr)
mongo.db.config.update({"name": "newconfig"}, {"$set": {mongo_key: value}})
mongo.db.config.update({}, {"$set": {mongo_key: value}})
@staticmethod
def get_flat_config(is_initial_config=False, should_decrypt=True):
config_json = ConfigService.get_config(is_initial_config, should_decrypt)
def get_flat_config(should_decrypt=True):
config_json = ConfigService.get_config(should_decrypt)
flat_config_json = {}
for i in config_json:
if i == "ransomware":
@ -153,7 +143,7 @@ class ConfigService:
@staticmethod
def add_item_to_config_set_if_dont_exist(item_path_array, item_value, should_encrypt):
item_key = ".".join(item_path_array)
items_from_config = ConfigService.get_config_value(item_path_array, False, should_encrypt)
items_from_config = ConfigService.get_config_value(item_path_array, should_encrypt)
if item_value in items_from_config:
return
if should_encrypt:
@ -161,13 +151,7 @@ class ConfigService:
item_value = encrypt_dict(SENSITIVE_SSH_KEY_FIELDS, item_value)
else:
item_value = get_datastore_encryptor().encrypt(item_value)
mongo.db.config.update(
{"name": "newconfig"}, {"$addToSet": {item_key: item_value}}, upsert=False
)
mongo.db.monkey.update(
{}, {"$addToSet": {"config." + item_key.split(".")[-1]: item_value}}, multi=True
)
mongo.db.config.update({}, {"$addToSet": {item_key: item_value}})
@staticmethod
def creds_add_username(username):
@ -225,7 +209,7 @@ class ConfigService:
except KeyError:
logger.error("Bad configuration file was submitted.")
return False
mongo.db.config.update({"name": "newconfig"}, {"$set": config_json}, upsert=True)
mongo.db.config.update({}, {"$set": config_json}, upsert=True)
logger.info("monkey config was updated")
return True
@ -293,17 +277,6 @@ class ConfigService:
ISLAND_PORT,
)
@staticmethod
def save_initial_config_if_needed():
if mongo.db.config.find_one({"name": "initial"}) is not None:
return
initial_config = mongo.db.config.find_one({"name": "newconfig"})
initial_config["name"] = "initial"
initial_config.pop("_id")
mongo.db.config.insert(initial_config)
logger.info("Monkey config was inserted to mongo and saved")
@staticmethod
def _extend_config_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"]
@ -350,34 +323,6 @@ class ConfigService:
def encrypt_config(config):
ConfigService._encrypt_or_decrypt_config(config, False)
@staticmethod
def decrypt_flat_config(flat_config, is_island=False):
"""
Same as decrypt_config but for a flat configuration
"""
keys = [config_arr_as_array[-1] for config_arr_as_array in ENCRYPTED_CONFIG_VALUES]
for key in keys:
if isinstance(flat_config[key], collections.Sequence) and not isinstance(
flat_config[key], str
):
# Check if we are decrypting ssh key pair
if (
flat_config[key]
and isinstance(flat_config[key][0], dict)
and "public_key" in flat_config[key][0]
):
flat_config[key] = [
decrypt_dict(SENSITIVE_SSH_KEY_FIELDS, item) for item in flat_config[key]
]
else:
flat_config[key] = [
get_datastore_encryptor().decrypt(item) for item in flat_config[key]
]
else:
flat_config[key] = get_datastore_encryptor().decrypt(flat_config[key])
return flat_config
@staticmethod
def _encrypt_or_decrypt_config(config, is_decrypt=False):
for config_arr_as_array in ENCRYPTED_CONFIG_VALUES:
@ -427,11 +372,13 @@ class ConfigService:
}
@staticmethod
def format_flat_config_for_agent(config: Dict):
def format_flat_config_for_agent():
config = ConfigService.get_flat_config()
ConfigService._remove_credentials_from_flat_config(config)
ConfigService._format_payloads_from_flat_config(config)
ConfigService._format_pbas_from_flat_config(config)
ConfigService._format_propagation_from_flat_config(config)
return config
@staticmethod
def _remove_credentials_from_flat_config(config: Dict):

View File

@ -383,11 +383,11 @@ class ReportService:
@staticmethod
def get_config_users():
return ConfigService.get_config_value(USER_LIST_PATH, True, True)
return ConfigService.get_config_value(USER_LIST_PATH, True)
@staticmethod
def get_config_passwords():
return ConfigService.get_config_value(PASSWORD_LIST_PATH, True, True)
return ConfigService.get_config_value(PASSWORD_LIST_PATH, True)
@staticmethod
def get_config_exploits():
@ -395,7 +395,7 @@ class ReportService:
default_exploits = ConfigService.get_default_config(False)
for namespace in exploits_config_value:
default_exploits = default_exploits[namespace]
exploits = ConfigService.get_config_value(exploits_config_value, True, True)
exploits = ConfigService.get_config_value(exploits_config_value, True)
if exploits == default_exploits:
return ["default"]
@ -406,11 +406,11 @@ class ReportService:
@staticmethod
def get_config_ips():
return ConfigService.get_config_value(SUBNET_SCAN_LIST_PATH, True, True)
return ConfigService.get_config_value(SUBNET_SCAN_LIST_PATH, True)
@staticmethod
def get_config_scan():
return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True, True)
return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True)
@staticmethod
def get_issue_set(issues, config_users, config_passwords):

View File

@ -11,6 +11,13 @@ def mock_port(monkeypatch, PORT):
monkeypatch.setattr("monkey_island.cc.services.config.ISLAND_PORT", PORT)
@pytest.fixture(autouse=True)
def mock_flat_config(monkeypatch, flat_monkey_config):
monkeypatch.setattr(
"monkey_island.cc.services.config.ConfigService.get_flat_config", lambda: flat_monkey_config
)
@pytest.mark.slow
@pytest.mark.usefixtures("uses_encryptor")
def test_set_server_ips_in_config_command_servers(config, IPS, PORT):
@ -27,8 +34,8 @@ def test_set_server_ips_in_config_current_server(config, IPS, PORT):
assert config["internal"]["island_server"]["current_server"] == expected_config_current_server
def test_format_config_for_agent__credentials_removed(flat_monkey_config):
ConfigService.format_flat_config_for_agent(flat_monkey_config)
def test_format_config_for_agent__credentials_removed():
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "exploit_lm_hash_list" not in flat_monkey_config
assert "exploit_ntlm_hash_list" not in flat_monkey_config
@ -37,7 +44,7 @@ def test_format_config_for_agent__credentials_removed(flat_monkey_config):
assert "exploit_user_list" not in flat_monkey_config
def test_format_config_for_agent__ransomware_payload(flat_monkey_config):
def test_format_config_for_agent__ransomware_payload():
expected_ransomware_options = {
"ransomware": {
"encryption": {
@ -51,7 +58,7 @@ def test_format_config_for_agent__ransomware_payload(flat_monkey_config):
}
}
ConfigService.format_flat_config_for_agent(flat_monkey_config)
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "payloads" in flat_monkey_config
assert flat_monkey_config["payloads"] == expected_ransomware_options
@ -59,7 +66,7 @@ def test_format_config_for_agent__ransomware_payload(flat_monkey_config):
assert "ransomware" not in flat_monkey_config
def test_format_config_for_agent__pbas(flat_monkey_config):
def test_format_config_for_agent__pbas():
expected_pbas_config = {
"CommunicateAsBackdoorUser": {},
"ModifyShellStartupFiles": {},
@ -67,7 +74,7 @@ def test_format_config_for_agent__pbas(flat_monkey_config):
"Timestomping": {},
"AccountDiscovery": {},
}
ConfigService.format_flat_config_for_agent(flat_monkey_config)
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "post_breach_actions" in flat_monkey_config
assert flat_monkey_config["post_breach_actions"] == expected_pbas_config
@ -78,7 +85,7 @@ def test_format_config_for_agent__pbas(flat_monkey_config):
assert "PBA_windows_filename" not in flat_monkey_config
def test_format_config_for_custom_pbas(flat_monkey_config):
def test_format_config_for_custom_pbas():
custom_config = {
"linux_command": "bash test.sh",
"windows_command": "powershell test.ps1",
@ -86,7 +93,7 @@ def test_format_config_for_custom_pbas(flat_monkey_config):
"windows_filename": "test.ps1",
"current_server": "10.197.94.72:5000",
}
ConfigService.format_flat_config_for_agent(flat_monkey_config)
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert flat_monkey_config["custom_pbas"] == custom_config
@ -104,8 +111,8 @@ def test_get_config_propagation_credentials_from_flat_config(flat_monkey_config)
assert creds == expected_creds
def test_format_config_for_agent__propagation(flat_monkey_config):
ConfigService.format_flat_config_for_agent(flat_monkey_config)
def test_format_config_for_agent__propagation():
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "propagation" in flat_monkey_config
assert "targets" in flat_monkey_config["propagation"]
@ -113,7 +120,7 @@ def test_format_config_for_agent__propagation(flat_monkey_config):
assert "exploiters" in flat_monkey_config["propagation"]
def test_format_config_for_agent__propagation_targets(flat_monkey_config):
def test_format_config_for_agent__propagation_targets():
expected_targets = {
"blocked_ips": ["192.168.1.1", "192.168.1.100"],
"inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"],
@ -121,7 +128,7 @@ def test_format_config_for_agent__propagation_targets(flat_monkey_config):
"subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"],
}
ConfigService.format_flat_config_for_agent(flat_monkey_config)
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert flat_monkey_config["propagation"]["targets"] == expected_targets
assert "blocked_ips" not in flat_monkey_config
@ -130,7 +137,7 @@ def test_format_config_for_agent__propagation_targets(flat_monkey_config):
assert "subnet_scan_list" not in flat_monkey_config
def test_format_config_for_agent__network_scan(flat_monkey_config):
def test_format_config_for_agent__network_scan():
expected_network_scan_config = {
"tcp": {
"timeout_ms": 3000,
@ -164,7 +171,7 @@ def test_format_config_for_agent__network_scan(flat_monkey_config):
{"name": "ssh", "options": {}},
],
}
ConfigService.format_flat_config_for_agent(flat_monkey_config)
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "propagation" in flat_monkey_config
assert "network_scan" in flat_monkey_config["propagation"]
@ -176,7 +183,7 @@ def test_format_config_for_agent__network_scan(flat_monkey_config):
assert "finger_classes" not in flat_monkey_config
def test_format_config_for_agent__exploiters(flat_monkey_config):
def test_format_config_for_agent__exploiters():
expected_exploiters_config = {
"options": {
"http_ports": [80, 443, 7001, 8008, 8080, 9200],
@ -202,7 +209,7 @@ def test_format_config_for_agent__exploiters(flat_monkey_config):
{"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}},
],
}
ConfigService.format_flat_config_for_agent(flat_monkey_config)
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "propagation" in flat_monkey_config
assert "exploiters" in flat_monkey_config["propagation"]