Mini Shell
"""
Usage /opt/imunify360/venv/bin/python3 rules_checker.py <action>
choose an action from ACTIONS map
e.g.
/opt/imunify360/venv/bin/python3 rules_checker.py recreate
Actions:
- `recreate` - recreates rules if needed and checks ipsets consistent
- `clear` - waits RulesChecker stop and destroys all rules and ipsets
Actions based on lazy_init plugin
placed in im360.plugins.protector.lazy_init
"""
import asyncio
from pathlib import Path
import os
import pickle
import sys
import time
from defence360agent.internals import logger as lg
from defence360agent.internals.global_scope import g
from defence360agent.model import instance, tls_check
from defence360agent.contracts.config import Model, Merger
from im360.contracts.config import IPSET_LISTS_PATH
from im360.files import WHITELISTS, Index
from im360.internals.core import ip_versions
from im360.internals.strategy import Strategy
from im360.plugins.protector import RULES_CHECK_IN_PROGRESS
from im360.plugins.protector.lazy_init import (
RulesChecker,
RealProtector,
)
from im360.subsys import smtp_blocking
from logging import getLogger
logger = getLogger("rules-checker")
STATE = {"last_ipset_check": 0.0}
DAY = 24 * 60 * 60
async def _check_for_config_change(rc: RulesChecker, rp: RealProtector):
"""Checking that config state is consistent with the current state."""
rp._rules_checker = rc
await rp._rules_checker.check_smtp_state_and_reset()
await rp._on_config_update_unlocked(None)
async def recreate_rules(rc: RulesChecker, rp: RealProtector):
"""Recreates rules if needed and checks ipsets consistent."""
logger.info("Checking that need to recreate rules")
# TODO: check if we need to check it too often
# for Python implementation we do it only once per day
if time.time() - STATE["last_ipset_check"] < DAY:
logger.info("Skip ipsets check")
else:
await rc._check_ipsets_consistent()
STATE["last_ipset_check"] = time.time()
await rc.recreate_rules_if_needed()
await _check_for_config_change(rc, rp)
logger.info("IP sets verification and initialization completed")
async def check_config_update(rc: RulesChecker, rp: RealProtector):
"""Checking config update."""
logger.info("Checking config update")
await _check_for_config_change(rc, rp)
logger.info("Completed")
async def recreate_rules_on_strategy_change(
rc: RulesChecker, rp: RealProtector
):
"""Recreates rules if needed and checks ipsets consistent."""
logger.info("Checking that need to recreate rules on strategy change")
await rc.recreate_rules_if_needed()
logger.info(
"Firewall rules recreated due to StrategyChange %s", Strategy.current
)
async def check_ipsets_consistent(rc: RulesChecker, rp: RealProtector):
"""Check ipsets consistent."""
logger.info("Checking ipsets consistent")
await rc._check_ipsets_consistent()
STATE["last_ipset_check"] = time.time()
if any(sets for sets in rc.outdated_ipsets.values()):
await rc.recreate_rules_if_needed()
await _check_for_config_change(rc, rp)
logger.info("Completed")
async def clear_everything(rc: RulesChecker, rp: RealProtector):
"""Clear rules and ipsets on stop."""
logger.info("Clear rules and ipsets")
rc.should_stop()
await rc.wait()
await rc.clear_everything()
logger.info("Completed")
def setup_environment():
"""Setup environment for rules checker."""
lg.reconfigure()
ip_versions.init()
instance.db.init(Model.PATH)
instance.db.execute_sql("ATTACH ? AS resident", (Model.RESIDENT_PATH,))
instance.db.execute_sql("ATTACH ? AS ipsetlists", (IPSET_LISTS_PATH,))
if os.environ.get("DEBUG") == "true":
g.DEBUG = True
Index.add_type(WHITELISTS, "whitelist/v2", 0o770, 0o660, all_zip=True)
class RealProtectorState:
"""RealProtector state to save and restore."""
def __init__(self, _ws, _pb_dmv, _pbm, _lic):
self._webshield_status = _ws
self._port_blocking_deny_mode_values = _pb_dmv
self._port_blocking_mode = _pbm
self.last_ipset_check = _lic
def __str__(self) -> str:
return (
"RealProtectorState("
f"_webshield_status={self._webshield_status}, "
f"_port_blocking_mode={self._port_blocking_mode}, "
"_port_blocking_deny_mode_values"
f"={self._port_blocking_deny_mode_values},"
f"last_ipset_check={self.last_ipset_check})"
)
class RulesCheckerState:
def __init__(self, interface_conf, ipset_outdated_events, outdated_ipsets):
self._interface_conf = interface_conf
self._ipsets_outdated_events = ipset_outdated_events
self.outdated_ipsets = outdated_ipsets
def __str__(self) -> str:
return (
"RulesCheckerState("
f"_interface_conf={self._interface_conf}, "
f"_ipsets_outdated_events={self._ipsets_outdated_events})"
f"outdated_ipsets={self.outdated_ipsets})"
)
class SmtpSettingsState:
def __init__(self, active_settings):
self._active_settings = active_settings
def __str__(self) -> str:
return f"SmtpSettingsState(_active_settings={self._active_settings})"
def restore_state(rp: RealProtector, rc: RulesChecker):
"""Restore RealProtector state."""
Strategy.current = Strategy.get()
try:
if REAL_PROTECTOR_STATE.exists():
rp_state = pickle.load(REAL_PROTECTOR_STATE.open("rb"))
rp._webshield_status = rp_state._webshield_status
rp._port_blocking_deny_mode_values = (
rp_state._port_blocking_deny_mode_values
)
rp._port_blocking_mode = rp_state._port_blocking_mode
STATE["last_ipset_check"] = rp_state.last_ipset_check
except Exception as e:
logger.error("Failed to restore RealProtector state: %s", e)
try:
if RULES_CHECKER_STATE.exists():
rc_state = pickle.load(RULES_CHECKER_STATE.open("rb"))
rc.active_interface_conf = rc_state._interface_conf
rc._ipsets_outdated_events = rc_state._ipsets_outdated_events
rc.outdated_ipsets = rc_state.outdated_ipsets
except Exception as e:
logger.error("Failed to restore RulesChecker state: %s", e)
try:
if SMTP_BLOCKING_STATE.exists():
smtp_state = pickle.load(SMTP_BLOCKING_STATE.open("rb"))
for idx, version in enumerate(smtp_blocking.ip_versions.enabled()):
smtp_instance = smtp_blocking.SMTPBlocking(version)
smtp_instance.active_settings = smtp_state._active_settings[
idx
]
except Exception as e:
logger.error("Failed to restore SMTPBlocking state: %s", e)
return rp, rc
def save_state(rp: RealProtector, rc: RulesChecker):
"""Save RealProtector state."""
rp_state = RealProtectorState(
rp._webshield_status,
rp._port_blocking_deny_mode_values,
rp._port_blocking_mode,
STATE["last_ipset_check"],
)
rc_state = RulesCheckerState(
rc.active_interface_conf,
rc._ipsets_outdated_events,
rc.outdated_ipsets,
)
smtp_state = SmtpSettingsState(smtp_blocking.get_active_settings_list())
pickle.dump(rp_state, REAL_PROTECTOR_STATE.open("wb"))
pickle.dump(rc_state, RULES_CHECKER_STATE.open("wb"))
pickle.dump(smtp_state, SMTP_BLOCKING_STATE.open("wb"))
ACTIONS = {
"recreate": recreate_rules,
"clear": clear_everything,
"config-update": check_config_update,
"strategy-change": recreate_rules_on_strategy_change,
"ipsets-consistent": check_ipsets_consistent,
}
REAL_PROTECTOR_STATE = Path("/var/imunify360/.realprotector.state")
RULES_CHECKER_STATE = Path("/var/imunify360/.ruleschecker.state")
SMTP_BLOCKING_STATE = Path("/var/imunify360/.smtp_blocking.state")
def main(action):
try:
RULES_CHECK_IN_PROGRESS.touch()
except Exception as e:
logger.error("Failed to create RULES_CHECK_IN_PROGRESS file: %s", e)
tls_check.reset()
setup_environment()
try:
Merger.update_merged_config()
except Exception as e:
logger.error("Failed to update merged config: %r", e)
loop = asyncio.get_event_loop()
rp, rc = restore_state(RealProtector(), RulesChecker(loop))
if action not in ACTIONS:
print(
f"Invalid action: {action}. "
f"Choose from {', '.join(ACTIONS.keys())}"
)
RULES_CHECK_IN_PROGRESS.unlink(missing_ok=True)
sys.exit(1)
action = ACTIONS[action]
loop.run_until_complete(action(rc, rp))
save_state(rp, rc)
RULES_CHECK_IN_PROGRESS.unlink(missing_ok=True)
logger.info("Script finished")
if __name__ == "__main__":
if len(sys.argv) != 2:
print(
"Please provide one action as command line argument."
f" {', '.join(ACTIONS.keys())}"
)
sys.exit(1)
main(sys.argv[1])
Zerion Mini Shell 1.0