????
Your IP : 3.12.76.129
import asyncio
import functools
import logging
import time
from peewee import DoesNotExist, IntegrityError
from defence360agent.contracts.messages import MessageType
from defence360agent.model.simplification import run_in_executor
from defence360agent.rpc_tools import ValidationError, lookup
from defence360agent.rpc_tools.utils import run_in_executor_decorator
from defence360agent.subsys import web_server
from defence360agent.utils import Scope
from im360.contracts.config import Modsec, Permissions
from im360.model.incident import DisabledRule, DisabledRuleDomain
from im360.subsys import modsec_app_version_detector
from im360.subsys import shared_disabled_rules as disabled_rules
from im360.subsys import waf_rules_configurator
from im360.subsys.panels import hosting_panel
from im360.subsys.panels.generic.mod_security import (
GenericPanelModSecException,
)
logger = logging.getLogger(__name__)
class DisabledRulesEndpoints(lookup.RootEndpoints):
SCOPE = Scope.IM360
def __init__(self, sink):
super().__init__(sink)
self.hp = hosting_panel.HostingPanel()
@lookup.bind("rules", "disable")
async def disable_rule(self, plugin, id, name, domains=None):
self.__check_edit_is_enabled()
domains = domains or []
if domains and plugin != "modsec":
raise ValidationError("Domains only allowed for plugin=modsec")
# validate domain
panel_domains = set(await self.hp.get_user_domains())
if not set(domains).issubset(panel_domains):
raise ValidationError(
"Some of the provided domains do not exist: {}".format(
set(domains) - panel_domains,
)
)
# NOTE: we can't call _store_disabled_rule after
# _sync_modsec_configs, because we need to form a union of
# specified domains and domains for which the specified rule is
# already disabled. We might refactor this method in DEF-10761.
sync_domains = await self._store_disabled_rule(
plugin, id, name, domains
)
if plugin == "modsec":
await self._sync_modsec_configs(set(sync_domains) & panel_domains)
await self._sink.process_message(
MessageType.RuleDisabled(
plugin_id=plugin,
rule=id,
name=name,
domains=(domains or None),
timestamp=time.time(),
)
)
async def _delete_disabled_rule(self, plugin, id):
loop = asyncio.get_event_loop()
await run_in_executor(
loop,
lambda: DisabledRule.delete()
.where(DisabledRule.plugin == plugin, DisabledRule.rule_id == id)
.execute(),
)
@lookup.bind("rules", "enable")
async def enable_rule(self, plugin, id):
self.__check_edit_is_enabled()
loop = asyncio.get_event_loop()
try:
dr = await run_in_executor(
loop, lambda: DisabledRule.get(plugin=plugin, rule_id=id)
)
except DoesNotExist:
return
if plugin == "modsec":
domains = [
d[0]
for d in await run_in_executor(
loop,
lambda: DisabledRuleDomain.select(
DisabledRuleDomain.domain
)
.where(DisabledRuleDomain.disabled_rule_id_id == dr.id)
.tuples(),
)
]
await self._delete_disabled_rule(plugin, id)
panel_domains = set(await self.hp.get_user_domains())
await self._sync_modsec_configs(set(domains) & panel_domains)
else:
await self._delete_disabled_rule(plugin, id)
await self._sink.process_message(
MessageType.RuleEnabled(
plugin_id=plugin, rule=id, timestamp=time.time()
)
)
@lookup.bind("rules", "list-disabled")
@run_in_executor_decorator
def list_disabled_rules(self, limit, offset, order_by=None):
return DisabledRule.fetch(limit, offset, order_by)
@lookup.bind("rules", "update-app-specific-rules")
async def update_app_based_rules(self):
if not Modsec.APP_SPECIFIC_RULESET:
raise ValidationError("App specific ruleset setting is disabled.")
try:
await waf_rules_configurator.update_waf_rules_config()
except (
waf_rules_configurator.NotSupportedWebserverError,
modsec_app_version_detector.DatabaseNotFoundError,
NotImplementedError,
) as e:
raise ValidationError(str(e))
@lookup.bind("rules", "update-shared-disabled-rules")
async def list_update_shared_disabled_rules(self):
rules_file = disabled_rules.get_shared_disabled_modsec_rules_ids()
rules_db = set(DisabledRule.get_global_disabled("modsec"))
rules = list([str(rule) for rule in rules_db.union(rules_file)])
logger.info(
"Syncing shared disabled rules: %s",
" ".join(rules) if rules else "(empty)",
)
await self.hp.sync_global_disabled_rules(rules)
await web_server.graceful_restart()
@run_in_executor_decorator
def _store_disabled_rule(self, plugin, id, name, domains):
sync_domains = set(domains)
try:
inserted_id = DisabledRule.insert(
plugin=plugin, rule_id=id, name=name
).execute()
except IntegrityError:
dr = DisabledRule.get(plugin=plugin, rule_id=id)
for d in DisabledRuleDomain.select().where(
DisabledRuleDomain.disabled_rule_id_id == dr.id
):
sync_domains.add(d.domain)
DisabledRuleDomain.delete().where(
DisabledRuleDomain.disabled_rule_id_id == dr.id
).execute()
for d in domains:
DisabledRuleDomain.create_or_get(
disabled_rule_id_id=dr.id, domain=d
)
else:
for d in domains:
DisabledRuleDomain.create(
disabled_rule_id_id=inserted_id, domain=d
)
return list(sync_domains)
async def _sync_modsec_configs(self, domains: set):
loop = asyncio.get_event_loop()
domain_list = list(domains)
rules_list = await asyncio.gather(
*(
run_in_executor(
loop,
functools.partial(
DisabledRule.get_domain_disabled, "modsec", d
),
)
for d in domain_list
)
)
try:
if domain_list:
await self.hp.sync_disabled_rules_for_domains(
dict(zip(domain_list, rules_list))
)
except GenericPanelModSecException as e:
# don't send errors from generic panel to Sentry;
# panel admin is responsible for configuring generic panel
raise ValidationError(str(e)) from e
rules = await run_in_executor(
loop, lambda: DisabledRule.get_global_disabled("modsec")
)
await self.hp.sync_global_disabled_rules(rules)
await web_server.graceful_restart()
@staticmethod
def __check_edit_is_enabled():
if not Permissions.ALLOW_LOCAL_RULES_MANAGEMENT:
raise ValidationError("Local rule management is disabled.")