How does looping work

Describe the problem/error/question

how do i actually implement the loop node, my wazuh is sending vpn logs which are tunnel up and down so im trying to get it to if tunnel down wait 10 seconds for tunnel up if not proceed to create alert, if tunnel up comes within 10 seconds then drop the log, so im trying to figure out how i can get n8n to look at multiple events

im quite new to n8n so please if you need anymore details i will try to provide

What is the error message (if any)?

Please share your workflow

Information on your n8n setup

  • n8n version:1.23
  • Database (default: SQLite):
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • **Running n8n via (Docker, npm, n8n cloud, desktop app):npm
  • **Operating system:ubuntu 22.04
// Initialize output items array
const outputItems = [];

// Create a temporary storage for VPN Tunnel alerts
const vpnTunnels = {};

// Define a delay in milliseconds for waiting for a "Tunnel Up" alert
const waitDelay = 10000; // 10 seconds

// Helper function to process the "Tunnel Up" alert
function processTunnelUp(alert, rule, agent, description, artifacts) {
    const sourceRef = Math.random().toString(36).substr(2, 6);

    const alertPayload = {
        title: rule.description || "No title available",
        tlp: 2,
        tags: [
            "wazuh",
            `rule=${rule.id || "unknown"}`,
            `agent_name=${agent.name || "unknown"}`,
            `agent_id=${agent.id || "unknown"}`,
            `agent_ip=${agent.ip || "unknown"}`,
        ],
        description: description,
        type: "wazuh_alert",
        source: "wazuh",
        sourceRef: sourceRef,
        artifacts: artifacts,
        raw: alert, // Include all fields from the Wazuh alert
        vpn_status: "up", // Indicate the VPN tunnel status
    };

    console.log("Processed Tunnel Up:", alertPayload); // Debugging log
    outputItems.push({ json: alertPayload });
}

// Helper function to process the "Tunnel Down" alert
function processTunnelDown(alert, rule, agent, description, artifacts) {
    const sourceRef = Math.random().toString(36).substr(2, 6);

    const alertPayload = {
        title: rule.description || "No title available",
        tlp: 2,
        tags: [
            "wazuh",
            `rule=${rule.id || "unknown"}`,
            `agent_name=${agent.name || "unknown"}`,
            `agent_id=${agent.id || "unknown"}`,
            `agent_ip=${agent.ip || "unknown"}`,
        ],
        description: description,
        type: "wazuh_alert",
        source: "wazuh",
        sourceRef: sourceRef,
        artifacts: artifacts,
        raw: alert, // Include all fields from the Wazuh alert
        vpn_status: "down", // Indicate the VPN tunnel status
    };

    console.log("Processed Tunnel Down:", alertPayload); // Debugging log
    outputItems.push({ json: alertPayload });
}

// Function to process the alerts synchronously
async function processAlerts(items) {
    console.log("Processing Alerts:", items.length); // Debugging log
    for (const item of items) {
        // Extract the alert data from the input item
        const alert = item.json.body.all_fields;

        if (!alert) {
            console.log("No alert data found in item:", item); // Debugging log
            continue;
        }

        // Ensure alert has required fields
        const rule = alert.rule || {};
        const agent = alert.agent || {};
        const data = alert.data || {};
        const vpntunnel = data.vpntunnel || "unknown"; // Use the vpntunnel field as the identifier
        const description = alert.full_log || "No description available";

        // Extract IPs, URLs, and domains from the description
        const ipAddresses = description.match(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g) || [];
        const urls = description.match(/http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+/g) || [];
        const domains = urls.map((url) => url.split("//")[1].split("/")[0]);

        // Prepare artifacts for The Hive
        const artifacts = [
            ...ipAddresses.map((ip) => ({ dataType: "ip", data: ip })),
            ...urls.map((url) => ({ dataType: "url", data: url })),
            ...domains.map((domain) => ({ dataType: "domain", data: domain })),
        ];

        const timestamp = new Date(alert.timestamp).getTime();

        // Process the alert based on the rule description
        if (rule.description === "Fortigate: VPN Tunnel Down") {
            console.log(`Tunnel Down detected for ${vpntunnel} at ${timestamp}`); // Debugging log
            // Store the "Tunnel Down" alert with its timestamp and set a timeout
            vpnTunnels[vpntunnel] = {
                status: "Down",
                timestamp,
                alert,
                artifacts,
                rule,
                agent,
                description,
                timeoutId: setTimeout(() => {
                    // If no "Tunnel Up" alert arrives within the delay, process the "Tunnel Down"
                    processTunnelDown(alert, rule, agent, description, artifacts);
                    delete vpnTunnels[vpntunnel];
                }, waitDelay),
            };
        } else if (rule.description === "Fortigate: VPN Tunnel Up") {
            console.log(`Tunnel Up detected for ${vpntunnel} at ${timestamp}`); // Debugging log
            // Check if there's a pending "Tunnel Down" alert
            if (vpnTunnels[vpntunnel] && vpnTunnels[vpntunnel].status === "Down") {
                // Calculate the time difference
                const timeDiff = timestamp - vpnTunnels[vpntunnel].timestamp;

                if (timeDiff <= waitDelay) {
                    // Clear the timeout for the "Tunnel Down" alert
                    clearTimeout(vpnTunnels[vpntunnel].timeoutId);

                    // Send the "Tunnel Up" alert
                    processTunnelUp(alert, rule, agent, description, artifacts);
                }

                // Remove the "Tunnel Down" alert from the storage
                delete vpnTunnels[vpntunnel];
            } else {
                // Directly process the "Tunnel Up" alert if no "Down" was pending
                processTunnelUp(alert, rule, agent, description, artifacts);
            }
        }
    }
}

// Execute the function to process alerts
await processAlerts(items);

// Output the processed alerts
console.log("Output Items:", outputItems.length); // Debugging log
return outputItems;

this is my javascript FYI
let me know if theres anything else i should add

Hey @RyanInsolencee,

Welcome to the community :tada:

The loop node is typically used for single item processing, What you have now looks like it should do the job. Do you see any errors with what you are doing?

We do have a small section in the docs here: Looping | n8n Docs which talks about how looping works in n8n if it helps.

because the tunnel up and down are different events how do i get it to listen for another event

right now im only seeing 1 item per execution is there a way to allow more then 1 item in which is 1 the tunnel down log and 2 the tunnel up log

any advice ?

Hey @RyanInsolencee,

Is the code approach not working to check the logs of Wazuh for an up event? You could in theory have a workflow that has multiple listeners but each run won’t be aware of the other ones.

Maybe a temproary solution is to use something like Redis to store the down event and a timestamp then when you get an up event from another webhook you can do the up event processing.

the code waits for an up event but instead of having multiple events per run its running as 1 execution for 1 event

would it be possible to show me what it would look like ?

this is my current workflow for reference

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.