Passed
Push — develop ( 96460f...afdd08 )
by Портнов
04:07
created

IptablesConf::addAdditionalFirewallRules()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
c 0
b 0
f 0
dl 0
loc 25
rs 9.7333
cc 4
nc 4
nop 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\Core\System\Configs;
21
22
use MikoPBX\Common\Models\{FirewallRules, NetworkFilters, PbxSettings, Sip};
23
use MikoPBX\Core\Asterisk\Configs\SIPConf;
24
use MikoPBX\Core\System\Util;
25
use MikoPBX\Core\System\Processes;
26
use Phalcon\Di\Injectable;
27
28
class IptablesConf extends Injectable
29
{
30
    public const IP_TABLE_MIKO_CONF = '/etc/iptables/iptables.mikopbx';
31
    private bool $firewall_enable;
32
    private Fail2BanConf $fail2ban;
33
    private string $sipPort;
34
    private string $rtpPorts;
35
    private string $redisPort;
36
    private string $beanstalkPort;
37
38
    /**
39
     * Firewall constructor.
40
     */
41
    public function __construct()
42
    {
43
        $this->redisPort     = $this->getDI()->get('config')->redis->port;
44
        $this->beanstalkPort = $this->getDI()->get('config')->beanstalk->port;
45
46
        $firewall_enable       = PbxSettings::getValueByKey('PBXFirewallEnabled');
47
        $this->firewall_enable = ($firewall_enable === '1');
48
49
        $this->sipPort  = PbxSettings::getValueByKey('SIPPort');
50
        $defaultRTPFrom = PbxSettings::getValueByKey('RTPPortFrom');
51
        $defaultRTPTo   = PbxSettings::getValueByKey('RTPPortTo');
52
        $this->rtpPorts = "$defaultRTPFrom:$defaultRTPTo";
53
54
        $this->fail2ban = new Fail2BanConf();
55
    }
56
57
    /**
58
     * Applies iptables settings and restart firewall
59
     */
60
    public static function reloadFirewall(): void
61
    {
62
        $pid_file = '/var/run/service_reload_firewall.pid';
63
        if (file_exists($pid_file)) {
64
            $old_pid = file_get_contents($pid_file);
65
            $process = Processes::getPidOfProcess("^$old_pid");
66
            if ($process !== '') {
67
                return; // another restart process exists
68
            }
69
        }
70
        file_put_contents($pid_file, getmypid());
71
72
        $firewall = new self();
73
        $firewall->applyConfig();
74
        unlink($pid_file);
75
    }
76
77
    /**
78
     * Apples iptables settings
79
     **/
80
    public function applyConfig(): void
81
    {
82
        $this->fail2ban->fail2banStop();
83
        $this->dropAllRules();
84
        $this->fail2ban->fail2banMakeDirs();
85
86
        if ($this->firewall_enable) {
87
            $arr_command   = [];
88
            $arr_command[] = $this->getIptablesInputRule('', '-m conntrack --ctstate ESTABLISHED,RELATED');
89
            // Добавляем разрешения на сервисы.
90
            $this->addMainFirewallRules($arr_command);
91
            $this->addAdditionalFirewallRules($arr_command);
92
            // Кастомизация правил firewall.
93
            $arr_commands_custom = [];
94
            $out                 = [];
95
            Util::fileWriteContent('/etc/firewall_additional', '');
96
97
            $catPath     = Util::which('cat');
98
            $grepPath    = Util::which('grep');
99
            $busyboxPath = Util::which('busybox');
100
            $awkPath     = Util::which('awk');
101
            Processes::mwExec(
102
                "$catPath /etc/firewall_additional | $grepPath -v '|' | $grepPath -v '&'| $grepPath '^iptables' | $busyboxPath $awkPath -F ';' '{print $1}'",
103
                $arr_commands_custom
104
            );
105
106
            $dropCommand = $this->getIptablesInputRule('', '', 'DROP');
107
            if (Util::isSystemctl() && ! Util::isDocker()) {
108
                Util::mwMkdir('/etc/iptables');
109
                file_put_contents(self::IP_TABLE_MIKO_CONF, implode("\n", $arr_command));
110
                file_put_contents(
111
                    self::IP_TABLE_MIKO_CONF,
112
                    "\n" . implode("\n", $arr_commands_custom),
113
                    FILE_APPEND
114
                );
115
                file_put_contents(
116
                    self::IP_TABLE_MIKO_CONF,
117
                    "\n" . $dropCommand,
118
                    FILE_APPEND
119
                );
120
                $systemctlPath = Util::which('systemctl');
121
                Processes::mwExec("$systemctlPath restart mikopbx_iptables");
122
            } else {
123
                Processes::mwExecCommands($arr_command, $out, 'firewall');
124
                Processes::mwExecCommands($arr_commands_custom, $out, 'firewall_additional');
125
                // Все остальное запрещаем.
126
                Processes::mwExec($dropCommand);
127
            }
128
        }
129
130
        // Setup fail2ban
131
        if ($this->fail2ban->fail2ban_enable) {
132
            $this->fail2ban->writeConfig();
133
            $this->fail2ban->fail2banStart();
134
        } else {
135
            $this->fail2ban->fail2banStop();
136
        }
137
    }
138
139
    /**
140
     *  Flush all firewall rules
141
     */
142
    private function dropAllRules(): void
143
    {
144
        $iptablesPath = Util::which('iptables');
145
        Processes::mwExec("$iptablesPath -F");
146
        Processes::mwExec("$iptablesPath -X");
147
    }
148
149
    /**
150
     * Makes iptables rule string
151
     *
152
     * @param string $dport
153
     * @param string $other_data
154
     * @param string $action
155
     *
156
     * @return string
157
     */
158
    private function getIptablesInputRule(string $dport = '', string $other_data = '', string $action = 'ACCEPT'): string
159
    {
160
        $data_port = '';
161
        if (trim($dport) !== '') {
162
            $data_port = '--dport ' . $dport;
163
        }
164
        $other_data = trim($other_data);
165
166
        return "iptables -A INPUT $other_data $data_port -j $action";
167
    }
168
169
    /**
170
     * Makes iptables rules
171
     *
172
     * @param $arr_command
173
     */
174
    private function addAdditionalFirewallRules(&$arr_command): void
175
    {
176
        /** @var Sip $data */
177
        $db_data  = Sip::find("type = 'friend' AND ( disabled <> '1')");
178
        $sipHosts = SIPConf::getSipHosts();
179
180
        $hashArray = [];
181
        foreach ($db_data as $data) {
182
            $data = $sipHosts[$data->uniqid] ?? [];
183
            foreach ($data as $host) {
184
                if (in_array($host, $hashArray, true)) {
185
                    // Не допускаем повторения хост.
186
                    continue;
187
                }
188
                $hashArray[]   = $host;
189
                $arr_command[] = $this->getIptablesInputRule($this->sipPort, '-p tcp -s ' . $host . ' ');
190
                $arr_command[] = $this->getIptablesInputRule($this->sipPort, '-p udp -s ' . $host . ' ');
191
                $arr_command[] = $this->getIptablesInputRule($this->rtpPorts, '-p udp -s ' . $host . ' ');
192
            }
193
        }
194
        // Allow all local connections
195
        $arr_command[] = $this->getIptablesInputRule($this->redisPort, '-p tcp -s 127.0.0.1 ');
196
        $arr_command[] = $this->getIptablesInputRule($this->beanstalkPort, '-p tcp -s 127.0.0.1 ');
197
        $arr_command[] = $this->getIptablesInputRule('', '-s 127.0.0.1 ');
198
        unset($db_data, $sipHosts, $result, $hashArray);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result seems to be never defined.
Loading history...
199
    }
200
201
    /**
202
     * Формирование основных правил для iptables.
203
     * @param $arr_command
204
     */
205
    private function addMainFirewallRules(&$arr_command):void{
206
        /** @var FirewallRules $result */
207
        /** @var FirewallRules $rule */
208
        $result = FirewallRules::find('action="allow"');
209
        foreach ($result as $rule) {
210
            if ($rule->portfrom !== $rule->portto && trim($rule->portto) !== '') {
211
                $port = "$rule->portfrom:$rule->portto";
212
            } else {
213
                $port = $rule->portfrom;
214
            }
215
            /** @var NetworkFilters $network_filter */
216
            $network_filter = NetworkFilters::findFirst($rule->networkfilterid);
217
            if ($network_filter === null) {
218
                Util::sysLogMsg('Firewall', "network_filter_id not found $rule->networkfilterid", LOG_WARNING);
219
                continue;
220
            }
221
            if ('0.0.0.0/0' === $network_filter->permit && $rule->action !== 'allow') {
222
                continue;
223
            }
224
            $other_data = "-p $rule->protocol";
225
            $other_data .= ' -s ' . $network_filter->permit;
226
            if ($rule->protocol === 'icmp') {
227
                $port       = '';
228
                $other_data .= ' --icmp-type echo-request';
229
            }
230
            $action        = ($rule->action === 'allow') ? 'ACCEPT' : 'DROP';
231
            $arr_command[] = $this->getIptablesInputRule($port, $other_data, $action);
232
        }
233
    }
234
235
    /**
236
     * Updates firewall rules according to default template
237
     */
238
    public static function updateFirewallRules(): void
239
    {
240
        $portSet   = FirewallRules::getProtectedPortSet();
241
        $portNames = array_keys($portSet);
242
243
        $conditions = [
244
            'conditions' => 'portFromKey IN ({ids:array}) OR portToKey IN ({ids:array})',
245
            'bind'       => [
246
                'ids' => $portNames,
247
            ],
248
        ];
249
        $rules      = FirewallRules::find($conditions);
250
        foreach ($rules as $rule) {
251
            $rule->portfrom = $portSet[$rule->portFromKey]??'0';
252
            $rule->portto = $portSet[$rule->portToKey]??'0';
253
            $rule->update();
254
        }
255
    }
256
257
}
258