· 10 months ago · Nov 21, 2024, 06:25 AM
1#!/usr/bin/php
2<?php
3// Configuration
4define('CROWDSEC_API_URL', 'http://127.0.0.1:8080/v1/decisions'); // Adjust CrowdSec API URL
5define('CROWDSEC_API_KEY', 'Amk5bZTMZn42Df3r2Nthqvpn3RC1gRTfZ4MZvIl/lXE'); // Replace with your CrowdSec API key
6define('FIREWALLD_IPSETS_DIR', '/etc/firewalld/ipsets/');
7define('IPSET_NAME_IPV4', 'crowdsec-banned-ipv4');
8define('IPSET_NAME_IPV6', 'crowdsec-banned-ipv6');
9define('LOG_FILE', '/var/log/php-bouncer.log');
10define('SLEEP_INTERVAL', 60); // Set the interval for the service in seconds
11
12// Ensure this script is run from the command line
13//if (php_sapi_name() !== 'cli') {
14// die("This script can only be run from the command line.\n");
15//}
16
17// Function to fetch decisions from CrowdSec
18function fetchDecisions() {
19 $ch = curl_init();
20 curl_setopt($ch, CURLOPT_URL, CROWDSEC_API_URL);
21 curl_setopt($ch, CURLOPT_HTTPHEADER, [
22 'X-Api-Key: ' . CROWDSEC_API_KEY,
23 'Accept: application/json'
24 ]);
25
26 $userAgent = "Mozilla/5.0 (compatible; MyCustomUserAgent/1.0)";
27 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
28
29 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
30
31 $response = curl_exec($ch);
32 if (curl_errno($ch)) {
33 echo "Curl error: " . curl_error($ch);
34 return [];
35 }
36
37 curl_close($ch);
38 return json_decode($response, true);
39}
40
41// Function to separate IPv4 and IPv6 addresses
42function separateIpAddresses($decisions) {
43 $ipv4Addresses = [];
44 $ipv6Addresses = [];
45
46 foreach ($decisions as $decision) {
47 $ip = $decision['value'];
48 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
49 $ipv4Addresses[] = $ip;
50 } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
51 $ipv6Addresses[] = $ip;
52 }
53 }
54
55 return [$ipv4Addresses, $ipv6Addresses];
56}
57
58// Function to load existing ipset entries from an XML file
59function loadIpset($ipsetName) {
60 $ipsetFile = FIREWALLD_IPSETS_DIR . $ipsetName . '.xml';
61 $ipAddresses = [];
62
63 if (file_exists($ipsetFile)) {
64 $xml = simplexml_load_file($ipsetFile);
65 foreach ($xml->entry as $entry) {
66 $ipAddresses[] = (string) $entry;
67 }
68 }
69
70 return $ipAddresses;
71}
72
73// Function to update firewalld ipset file only if changes are needed
74function updateIpsetIfNeeded($ipAddresses, $ipsetName, $type, $iptype) {
75 $existingIps = loadIpset($ipsetName);
76 sort($ipAddresses);
77 sort($existingIps);
78
79 // Check if there are any changes
80 if ($ipAddresses === $existingIps) {
81 echo "No changes needed for $ipsetName.\n";
82 return false;
83 }
84
85 // Create new XML structure
86 $xml = new SimpleXMLElement('<ipset></ipset>');
87 $xml->addAttribute('type', $type); // 'hash:ip' for IPv4, 'hash:net' for IPv6
88
89 $opt = $xml->addChild('option');
90 $opt->addAttribute('name', 'family');
91 $opt->addAttribute('value', $iptype);
92
93 $opt = $xml->addChild('option');
94 $opt->addAttribute('name', 'hashsize');
95 $opt->addAttribute('value', '4096');
96
97 $opt = $xml->addChild('option');
98 $opt->addAttribute('name', 'maxelem');
99 $opt->addAttribute('value', '1000000');
100
101 foreach ($ipAddresses as $ip) {
102 $entry = $xml->addChild('entry');
103 $entry[0] = $ip;
104 }
105
106 // Save XML to ipset file
107 $ipsetFile = FIREWALLD_IPSETS_DIR . $ipsetName . '.xml';
108 $xml->asXML($ipsetFile);
109 echo "Updated $ipsetName with new entries.\n";
110 return true;
111}
112
113// Function to reload firewalld to apply changes
114function reloadFirewalld() {
115 exec('firewall-cmd --reload', $output, $status);
116 if ($status !== 0) {
117 echo "Error reloading firewalld: " . implode("\n", $output) . "\n";
118 }
119}
120
121function Run()
122{
123 // Main script logic
124 $decisions = fetchDecisions();
125 if (!empty($decisions)) {
126 list($ipv4Addresses, $ipv6Addresses) = separateIpAddresses($decisions);
127
128 // Track if any changes were made
129 $ipv4Updated = !empty($ipv4Addresses) ? updateIpsetIfNeeded($ipv4Addresses, IPSET_NAME_IPV4, 'hash:ip', 'inet') : false;
130 $ipv6Updated = !empty($ipv6Addresses) ? updateIpsetIfNeeded($ipv6Addresses, IPSET_NAME_IPV6, 'hash:net', 'inet6') : false;
131//$ipv4Updated = true;
132//$ipv6Updated = true;
133 // Reload firewalld only if there were changes
134 if ($ipv4Updated || $ipv6Updated) {
135 reloadFirewalld();
136 logMessage("firewalld reloaded with new CrowdSec decisions for IPv4 and IPv6.");
137 } else {
138 //logMessage("No changes detected. firewalld reload not required.");
139 }
140 }
141 else {
142 logMessage("No decisions to update.");
143 }
144// logMessage("Waiting for next iteration");
145//break;
146}
147
148
149// Flag to keep the service running
150$running = true;
151
152// Function to handle signals
153function signalHandler($signal) {
154 global $running;
155 switch ($signal) {
156 case SIGTERM:
157 case SIGINT:
158 logMessage("Received termination signal. Stopping...");
159 $running = false;
160 break;
161 }
162}
163
164// Attach signal handlers
165pcntl_signal(SIGTERM, "signalHandler");
166pcntl_signal(SIGINT, "signalHandler");
167
168// Function to log messages to a file
169function logMessage($message) {
170 file_put_contents(LOG_FILE, date('Y-m-d H:i:s') . " - $message\n", FILE_APPEND);
171}
172
173// The main service function that runs the program logic
174function service($isService = false) {
175 global $running;
176
177 logMessage("Service started.");
178
179 do {
180 run();
181 // Check for signals to allow graceful shutdown
182 pcntl_signal_dispatch();
183
184 // If running as a service, sleep for the specified interval before repeating
185 if ($isService && $running) {
186 sleep(SLEEP_INTERVAL);
187 }
188 } while ($isService && $running);
189
190 logMessage("Service stopped.");
191}
192
193function isRunningAsService() {
194 global $argv;
195 return in_array('--service', $argv);
196}
197
198//chdir('/root');
199// Check if running from the command line or as a systemd service
200if (isRunningAsService()) {
201 service(true);
202} else {
203 service(false);
204}
205?>
206