Passed
Push — develop ( 4feaea...be70e5 )
by Портнов
05:11 queued 13s
created

CloudProvisioning::updateWebPassword()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 7
rs 10
cc 3
nc 2
nop 2
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2021 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;
21
22
use MikoPBX\Common\Models\LanInterfaces;
23
use MikoPBX\Common\Models\PbxSettings;
24
use MikoPBX\Core\System\Configs\SSHConf;
25
use GuzzleHttp\Exception\GuzzleException;
26
use GuzzleHttp;
27
28
class CloudProvisioning
29
{
30
    public const PBX_SETTING_KEY = 'CloudProvisioning';
31
    public const HTTP_TIMEOUT = 10;
32
33
    public static function start():void
34
    {
35
        if(PbxSettings::findFirst('key="'.self::PBX_SETTING_KEY.'"')){
36
            // Уже отработали ранее.
37
            return;
38
        }
39
        $cp = new self();
40
41
        $solutions = ['google', 'mcs', 'azure'];
42
        $resultProvisioning = '0';
43
        foreach ($solutions as $solution){
44
            $methodName = "{$solution}Provisioning";
45
            if(!method_exists($cp, $methodName)){
46
                continue;
47
            }
48
            Util::sysLogMsg(__CLASS__, "Try $solution provisioning... ");
49
            if($cp->$methodName()){
50
                $resultProvisioning = '1';
51
                Util::sysLogMsg(__CLASS__, "$solution provisioning OK... ");
52
                break;
53
            }
54
        }
55
        $setting = PbxSettings::findFirst('key="'.self::PBX_SETTING_KEY.'"');
56
        if(!$setting){
57
            $setting = new PbxSettings();
58
            $setting->key = self::PBX_SETTING_KEY;
59
        }
60
        $setting->value = $resultProvisioning;
61
        $resultSave = $setting->save();
62
        unset($setting);
63
64
        if($resultSave && $resultProvisioning){
65
            $cp->checkConnectStorage();
66
        }
67
    }
68
69
    private function checkConnectStorage():void{
70
        $phpPath = Util::which('php');
71
        Processes::mwExec($phpPath.' -f /etc/rc/connect.storage auto');
72
    }
73
74
    private function updateSshPassword():void{
75
        $data = md5(time());
76
        $this->updatePbxSettings('SSHPassword', $data);
77
        $this->updatePbxSettings('SSHDisablePasswordLogins', '1');
78
        $confSsh = new SSHConf();
79
        $confSsh->updateShellPassword();
80
    }
81
82
    /**
83
     * Обновление пароля к SSH.
84
     * @param $keyName
85
     * @param $data
86
     */
87
    private function updatePbxSettings($keyName, $data):void{
88
        $setting = PbxSettings::findFirst('key="'.$keyName.'"');
89
        if(!$setting){
90
            $setting = new PbxSettings();
91
            $setting->key = $keyName;
92
        }
93
        $setting->value = $data;
94
        $result = $setting->save();
95
        if($result){
96
            Util::sysLogMsg(__CLASS__, "Update $keyName ... ");
97
        }else{
98
            Util::sysLogMsg(__CLASS__, "FAIL Update $keyName ... ");
99
        }
100
        unset($setting);
101
    }
102
103
    /**
104
     * Обновление ключей ssh.
105
     * @param string $data
106
     */
107
    private function updateSSHKeys(string $data):void{
108
        if(empty($data)){
109
            return;
110
        }
111
        $arrData = explode(':', $data);
112
        if(count($arrData) === 2){
113
            $data = $arrData[1];
114
        }
115
        $this->updatePbxSettings('SSHAuthorizedKeys', $data);
116
    }
117
118
    /**
119
     * Обновление имени хост.
120
     */
121
    private function updateLanSettings($hostname, $extipaddr):void{
122
        /** @var LanInterfaces $lanData */
123
        $lanData = LanInterfaces::findFirst();
124
        if($lanData){
0 ignored issues
show
introduced by
$lanData is of type MikoPBX\Common\Models\LanInterfaces, thus it always evaluated to true.
Loading history...
125
            if(!empty($extipaddr)){
126
                $lanData->extipaddr = $extipaddr;
127
                $lanData->topology  = 'private';
128
            }
129
            if(!empty($hostname)){
130
                $lanData->hostname  = $hostname;
131
            }
132
            $result = $lanData->save();
133
            if($result){
134
                Util::sysLogMsg(__CLASS__, 'Update LAN... '.$hostname.'  '. $extipaddr);
135
            }else{
136
                Util::sysLogMsg(__CLASS__, 'FAIL Update LAN... '.$hostname.'  '. $extipaddr);
137
            }
138
        }else{
139
            Util::sysLogMsg(__CLASS__, 'LAN not found... '.$hostname.'  '. $extipaddr);
140
        }
141
    }
142
143
    /**
144
     * Настройка машины для Google Cloud.
145
     */
146
    public function googleProvisioning():bool
147
    {
148
        $curl    = curl_init();
149
        $url     = 'http://169.254.169.254/computeMetadata/v1/instance/?recursive=true';
150
        curl_setopt($curl, CURLOPT_URL, $url);
151
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
152
        curl_setopt($curl, CURLOPT_TIMEOUT, self::HTTP_TIMEOUT);
153
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['Metadata-Flavor:Google']);
154
        $resultRequest = curl_exec($curl);
155
156
        $http_code     = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
157
        curl_close($curl);
158
        if($http_code !== 200 ){
159
            return false;
160
        }
161
        $data = json_decode($resultRequest, true);
0 ignored issues
show
Bug introduced by
It seems like $resultRequest can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
        $data = json_decode(/** @scrutinizer ignore-type */ $resultRequest, true);
Loading history...
162
        $this->updateSSHKeys($data['attributes']['ssh-keys']??'');
163
        $hostname = $data['name']??'';
164
        $extIp= $data['networkInterfaces'][0]['accessConfigs'][0]['externalIp']??'';
165
        $this->updateLanSettings($hostname, $extIp);
166
        $this->updateSshPassword();
167
        return true;
168
    }
169
170
    private function getMetaDataMCS($url):string
171
    {
172
        $baseUrl = 'http://169.254.169.254/latest/meta-data/';
173
        $client  = new GuzzleHttp\Client();
174
        $headers = [];
175
        $params  = [];
176
        $options = [
177
            'timeout' => 10,
178
            'http_errors' => false,
179
            'headers' => $headers
180
        ];
181
182
        $url = "$baseUrl/$url?".http_build_query($params);
183
        try {
184
            $res    = $client->request('GET', $url, $options);
185
            $code   = $res->getStatusCode();
186
        }catch (GuzzleHttp\Exception\ConnectException $e ){
187
            $code = 0;
188
        } catch (GuzzleException $e) {
189
            $code = 0;
190
        }
191
        $body = '';
192
        if($code === 200 && isset($res)){
193
            $body = $res->getBody()->getContents();
194
        }
195
        return $body;
196
    }
197
198
    /**
199
     * Автонастройка для Mail (VK) Cloud Solutions
200
     * @return bool
201
     */
202
    public function mcsProvisioning():bool
203
    {
204
        // Имя сервера.
205
        $hostname = $this->getMetaDataMCS('hostname');
206
        if(empty($hostname)){
207
            return false;
208
        }
209
        $extIp    = $this->getMetaDataMCS('public-ipv4');
210
        // Получим ключи ssh.
211
        $sshKey   = '';
212
        $sshKeys  = $this->getMetaDataMCS('public-keys');
213
        $sshId    = explode('=', $sshKeys)[0]??'';
214
        if($sshId !== ''){
215
            $sshKey = $this->getMetaDataMCS("public-keys/$sshId/openssh-key");
216
        }
217
        $this->updateSSHKeys($sshKey);
218
        $this->updateLanSettings($hostname, $extIp);
219
        $this->updateSshPassword();
220
221
        [$webLogin]  = explode('.', $hostname);
222
        $webPassword = $this->getMetaDataMCS('instance-id');
223
        $this->updateWebPassword($webLogin, $webPassword);
224
225
        return true;
226
    }
227
228
    /**
229
     * Устанавливает пароль к web интерфейсу исходя из имени инстанса и его идентификатора.
230
     * @param $webLogin
231
     * @param $webPassword
232
     * @return void
233
     */
234
    private function updateWebPassword($webLogin, $webPassword):void
235
    {
236
        if(empty($webLogin) || empty($webPassword)){
237
            return;
238
        }
239
        $this->updatePbxSettings('WebAdminLogin',   $webLogin);
240
        $this->updatePbxSettings('WebAdminPassword',$webPassword);
241
    }
242
243
    /**
244
     * Настройка машины для Azure Cloud
245
     */
246
    public function azureProvisioning():bool
247
    {
248
        $baseUrl = 'http://168.63.129.16/machine';
249
        $curl = curl_init();
250
        $url  = "{$baseUrl}?comp=goalstate";
251
        curl_setopt($curl, CURLOPT_URL, $url);
252
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
253
        curl_setopt($curl, CURLOPT_TIMEOUT, self::HTTP_TIMEOUT);
254
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['x-ms-version: 2012-11-30']);
255
        $resultRequest = curl_exec($curl);
256
        $http_code     = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
257
        curl_close($curl);
258
259
        if($http_code === 0){
260
            $setting = new PbxSettings();
261
            $setting->key = self::PBX_SETTING_KEY;
262
            $setting->save();
263
            $setting->value = '0';
264
            unset($setting);
265
            // It is not azure;
266
            return false;
267
        }
268
269
        $xml = simplexml_load_string($resultRequest);
0 ignored issues
show
Bug introduced by
It seems like $resultRequest can also be of type true; however, parameter $data of simplexml_load_string() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

269
        $xml = simplexml_load_string(/** @scrutinizer ignore-type */ $resultRequest);
Loading history...
270
        $xmlDocument = $this->getAzureXmlResponse($xml->Container->ContainerId, $xml->Container->RoleInstanceList->RoleInstance->InstanceId);
271
        $url="{$baseUrl}?comp=health";
272
        $headers = [
273
            'x-ms-version: 2012-11-30',
274
            'x-ms-agent-name: WALinuxAgent',
275
            'Content-Type: text/xml;charset=utf-8',
276
        ];
277
278
        $curl = curl_init();
279
        curl_setopt($curl, CURLOPT_URL, $url);
280
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
281
        curl_setopt($curl, CURLOPT_TIMEOUT, self::HTTP_TIMEOUT);
282
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
283
        curl_setopt($curl, CURLOPT_POST, true);
284
        curl_setopt($curl, CURLOPT_POSTFIELDS, $xmlDocument);
285
286
        curl_exec($curl);
287
        $http_code     = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
288
        $result = false;
289
        if($http_code === 200){
290
            $result = true;
291
        }
292
        $curl = curl_init();
293
        $url  = "http://169.254.169.254/metadata/instance?api-version=2020-09-01";
294
        curl_setopt($curl, CURLOPT_URL, $url);
295
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
296
        curl_setopt($curl, CURLOPT_TIMEOUT, self::HTTP_TIMEOUT);
297
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['Metadata:true']);
298
        $resultRequest = curl_exec($curl);
299
        curl_close($curl);
300
301
        $arrKeys = [];
302
        $jsonData = json_decode($resultRequest, true);
0 ignored issues
show
Bug introduced by
It seems like $resultRequest can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

302
        $jsonData = json_decode(/** @scrutinizer ignore-type */ $resultRequest, true);
Loading history...
303
        $publicKeys = $jsonData['compute']['publicKeys']??[];
304
        foreach ($publicKeys as $keeData){
305
            $arrKeys[]= $keeData['keyData'];
306
        }
307
        $this->updateSSHKeys(implode(PHP_EOL, $arrKeys));
308
        $this->updateSshPassword();
309
        return $result;
310
    }
311
312
    /**
313
     * Возвращает строку XML для ответа о готовкности машины.
314
     * @param $containerId
315
     * @param $instanceId
316
     * @return string
317
     */
318
    private function getAzureXmlResponse($containerId, $instanceId):string
319
    {
320
        return '<Health>
321
  <GoalStateIncarnation>1</GoalStateIncarnation>
322
  <Container>
323
    <ContainerId>'.$containerId.'</ContainerId>
324
    <RoleInstanceList>
325
      <Role>
326
        <InstanceId>'.$instanceId.'</InstanceId>
327
        <Health>
328
          <State>Ready</State>
329
        </Health>
330
      </Role>
331
    </RoleInstanceList>
332
  </Container>
333
</Health>';
334
    }
335
}