PHP 8.3.31
Preview: proactive_log_migration.py Size: 3.21 KB
/opt/imunify360/venv/lib64/python3.11/site-packages/defence360agent/feature_management/plugins/proactive_log_migration.py

"""Promote stale `proactive: na` perms to `log` when the deployment toggle
``FEATURE_MANAGEMENT.proactive_disable_target == "log"`` is set.

DEF-42523. Without this, customers (notably Cloudways) flipping the toggle
post-upgrade would only see the new behavior on users whose add-on state
changes; pre-existing disabled users would linger in NA / mode=DISABLED
indefinitely. Re-firing the proactive hook with LOG writes
``mode=LOG`` to user_config and updates the perm row.

Idempotent: once promoted, no NA proactive perms remain, so subsequent
boots no-op.
"""
import asyncio
from logging import getLogger

from defence360agent.contracts.config import ConfigFile
from defence360agent.contracts.plugins import MessageSink, thisguy
from defence360agent.feature_management.constants import LOG, NA, PROACTIVE
from defence360agent.feature_management.model import FeatureManagementPerms
from defence360agent.feature_management.utils import set_feature
from defence360agent.utils import Scope, create_task_and_log_exceptions

logger = getLogger(__name__)


@thisguy
class ProactiveLogMigration(MessageSink):
    # Proactive Defence is an Imunify360-only feature. ImunifyAV-only
    # installs have no proactive permissions to migrate and no
    # FEATURE_MANAGEMENT.proactive_disable_target schema key.
    SCOPE = Scope.IM360

    async def create_sink(self, loop: asyncio.AbstractEventLoop):
        # Spawn the migration as a background task so the agent's other
        # MessageSinks (and serving traffic) come up immediately. The
        # migration is idempotent on retry, so cancellation at shutdown
        # is safe. create_task_and_log_exceptions surfaces failures
        # to the agent's exception handler instead of silently dropping
        # them (the bare loop.create_task would).
        self._migration_task = create_task_and_log_exceptions(
            loop, self._migrate
        )

    async def shutdown(self):
        task = getattr(self, "_migration_task", None)
        if task is None or task.done():
            return
        task.cancel()
        try:
            await task
        except asyncio.CancelledError:
            pass

    @staticmethod
    async def _migrate():
        target = ConfigFile().get(
            "FEATURE_MANAGEMENT", "proactive_disable_target"
        )
        if target != "log":
            return

        users = [
            row.user
            for row in FeatureManagementPerms.select().where(
                (FeatureManagementPerms.proactive == NA)
                & (
                    FeatureManagementPerms.user
                    != FeatureManagementPerms.DEFAULT
                )
            )
        ]
        if not users:
            return

        logger.info(
            "Promoting %d proactive=na users to log (DEF-42523)",
            len(users),
        )
        promoted = 0
        for user in users:
            try:
                if await set_feature(user, PROACTIVE, LOG):
                    promoted += 1
            except Exception:
                logger.exception(
                    "Failed to promote proactive=na user %s to log", user
                )
        logger.info(
            "Promoted %d/%d users to proactive=log",
            promoted,
            len(users),
        )

Directory Contents

Dirs: 1 × Files: 3

Name Size Perms Modified Actions
- drwxr-xr-x 2026-06-08 20:23:14
Edit Download
3.32 KB lrw-r--r-- 2026-05-26 21:20:44
Edit Download
3.21 KB lrw-r--r-- 2026-05-26 21:20:44
Edit Download
0 B lrw-r--r-- 2026-05-26 21:20:44
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).