
­­­­­­­­­­­­­­­­­­
<!DOCTYPE html>
<html>
import asyncio

from defence360agent.model.simplification import run_in_executor
from defence360agent.rpc_tools.lookup import RootEndpoints, bind
from defence360agent.rpc_tools.utils import run_in_executor_decorator
from defence360agent.utils import Scope
from im360.contracts.config import Protector
from im360.contracts.messages import IpsetSync
from im360.internals.core.firewall import is_nftables_backend
from im360.simple_rpc.lists import raise_acquired_validation_error
from im360.simple_rpc.resident_socket import send_to_socket
from im360.subsys.webshield import update_remote_proxy_config
from im360.internals.core.ipset.ip import IPSetRemoteProxy
from im360.model.firewall import RemoteProxy, RemoteProxyGroup


@raise_acquired_validation_error
async def _send_msg_to_socket(msg):
    # IPSET_SYNC gets no reply, so waiting for one only buys back the read
    # timeout as an error; an unreachable socket still returns its own string.
    return await send_to_socket(msg=msg, wait_for_response=False)


class RemoteProxyEndpoints(RootEndpoints):
    SCOPE = Scope.IM360

    @bind("remote-proxy", "list")
    @run_in_executor_decorator
    def remote_proxy_list(self, by_group=None, by_source=None):
        return {"items": RemoteProxy.list(by_group, by_source, None)}

    @bind("remote-proxy", "add")
    async def remote_proxy_add(self, name, networks):
        await run_in_executor(
            asyncio.get_event_loop(),
            lambda: RemoteProxy.add_many(
                name, RemoteProxyGroup.MANUAL, networks
            ),
        )
        if is_nftables_backend():
            await _send_msg_to_socket({"method": IpsetSync.DEFAULT_METHOD})
        else:
            async with Protector.RULE_EDIT_LOCK:
                for network in networks:
                    await IPSetRemoteProxy().add(network, 0)
        await update_remote_proxy_config()

    @bind("remote-proxy", "delete")
    async def remote_proxy_delete(self, networks):
        deleted = await run_in_executor(
            asyncio.get_event_loop(),
            lambda: RemoteProxy.delete_networks(
                RemoteProxyGroup.MANUAL, networks
            ),
        )
        if is_nftables_backend():
            # Unconditional: a retry after a failed notification finds the rows
            # already gone, so gating on the delta would never resync.
            await _send_msg_to_socket({"method": IpsetSync.DEFAULT_METHOD})
            await update_remote_proxy_config()
            return
        async with Protector.RULE_EDIT_LOCK:
            for network in deleted:
                await IPSetRemoteProxy().delete(network)
        if len(deleted):
            await update_remote_proxy_config()

    @bind("remote-proxy", "group", "enable")
    async def remote_proxy_group_enable(self, name, source):
        loop = asyncio.get_event_loop()
        changed = await run_in_executor(
            loop, lambda: RemoteProxyGroup.set_enabled(name, source, True)
        )
        if is_nftables_backend():
            # Unconditional: a retry after a failed notification finds the group
            # already enabled, so gating on the delta would never resync.
            await _send_msg_to_socket({"method": IpsetSync.DEFAULT_METHOD})
            await update_remote_proxy_config()
            return
        if changed:
            networks = await run_in_executor(
                loop, lambda: RemoteProxy.list(name, source, None)
            )
            async with Protector.RULE_EDIT_LOCK:
                for network in networks:
                    await IPSetRemoteProxy().add(network["network"])
            if len(networks):
                await update_remote_proxy_config()

    @bind("remote-proxy", "group", "disable")
    async def remote_proxy_group_disable(self, name, source):
        loop = asyncio.get_event_loop()
        changed = await run_in_executor(
            loop, lambda: RemoteProxyGroup.set_enabled(name, source, False)
        )
        if is_nftables_backend():
            # Unconditional: a retry after a failed notification finds the group
            # already disabled, so gating on the delta would never resync.
            await _send_msg_to_socket({"method": IpsetSync.DEFAULT_METHOD})
            await update_remote_proxy_config()
            return
        if changed:
            networks = await run_in_executor(
                loop, lambda: RemoteProxy.list(name, source, None)
            )
            async with Protector.RULE_EDIT_LOCK:
                for network in networks:
                    await IPSetRemoteProxy().delete(network["network"])
            if len(networks):
                await update_remote_proxy_config()
