Passed
Push — develop ( 030ae4...a31311 )
by Портнов
04:34
created

CloudProvisioning::getMetaDataMCS()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 26
rs 9.2888
cc 5
nc 10
nop 1
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';
0 ignored issues
show
Unused Code introduced by
The assignment to $resultProvisioning is dead and can be removed.
Loading history...
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
        exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
56
        $setting = PbxSettings::findFirst('key="'.self::PBX_SETTING_KEY.'"');
0 ignored issues
show
Unused Code introduced by
$setting = MikoPBX\Commo...:PBX_SETTING_KEY . '"') is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
57
        if(!$setting){
58
            $setting = new PbxSettings();
59
            $setting->key = self::PBX_SETTING_KEY;
60
        }
61
        $setting->value = $resultProvisioning;
62
        $resultSave = $setting->save();
63
        unset($setting);
64
65
        if($resultSave && $resultProvisioning){
66
            // $cp->checkConnectStorage();
67
        }
68
    }
69
70
    private function checkConnectStorage():void{
71
        $phpPath = Util::which('php');
72
        Processes::mwExec($phpPath.' -f /etc/rc/connect.storage auto');
73
    }
74
75
    private function updateSshPassword():void{
76
        $data = md5(time());
77
        $this->updatePbxSettings('SSHPassword', $data);
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 Claud.
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
        $result = false;
205
        // Имя сервера.
206
        $hostname = $this->getMetaDataMCS('hostname');
207
        if(empty($hostname)){
208
            return $result;
209
        }
210
        $extIp    = $this->getMetaDataMCS('public-ipv4');
211
        // Получим ключи ssh.
212
        $sshKey   = '';
213
        $sshKeys  = $this->getMetaDataMCS('public-keys');
214
        $sshId    = explode('=', $sshKeys)[0]??'';
215
        if($sshId !== ''){
216
            $sshKey = $this->getMetaDataMCS("public-keys/$sshId/openssh-key");
217
        }
218
        $this->updateSSHKeys($sshKey);
219
        $this->updateLanSettings($hostname, $extIp);
220
        $this->updateSshPassword();
221
        return true;
222
    }
223
224
    /**
225
     * Настройка машины для Azure Claod.
226
     */
227
    public function azureProvisioning():bool
228
    {
229
        $baseUrl = 'http://168.63.129.16/machine';
230
        $curl = curl_init();
231
        $url  = "{$baseUrl}?comp=goalstate";
232
        curl_setopt($curl, CURLOPT_URL, $url);
233
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
234
        curl_setopt($curl, CURLOPT_TIMEOUT, self::HTTP_TIMEOUT);
235
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['x-ms-version: 2012-11-30']);
236
        $resultRequest = curl_exec($curl);
237
        $http_code     = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
238
        curl_close($curl);
239
240
        if($http_code === 0){
241
            $setting = new PbxSettings();
242
            $setting->key = self::PBX_SETTING_KEY;
243
            $setting->save();
244
            $setting->value = '0';
245
            unset($setting);
246
            // It is not azure;
247
            return false;
248
        }
249
250
        $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

250
        $xml = simplexml_load_string(/** @scrutinizer ignore-type */ $resultRequest);
Loading history...
251
        $xmlDocument = $this->getAzureXmlResponse($xml->Container->ContainerId, $xml->Container->RoleInstanceList->RoleInstance->InstanceId);
252
        $url="{$baseUrl}?comp=health";
253
        $headers = [
254
            'x-ms-version: 2012-11-30',
255
            'x-ms-agent-name: WALinuxAgent',
256
            'Content-Type: text/xml;charset=utf-8',
257
        ];
258
259
        $curl = curl_init();
260
        curl_setopt($curl, CURLOPT_URL, $url);
261
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
262
        curl_setopt($curl, CURLOPT_TIMEOUT, self::HTTP_TIMEOUT);
263
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
264
        curl_setopt($curl, CURLOPT_POST, true);
265
        curl_setopt($curl, CURLOPT_POSTFIELDS, $xmlDocument);
266
267
        curl_exec($curl);
268
        $http_code     = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
269
        $result = false;
270
        if($http_code === 200){
271
            $result = true;
272
        }
273
        $curl = curl_init();
274
        $url  = "http://169.254.169.254/metadata/instance?api-version=2020-09-01";
275
        curl_setopt($curl, CURLOPT_URL, $url);
276
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
277
        curl_setopt($curl, CURLOPT_TIMEOUT, self::HTTP_TIMEOUT);
278
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['Metadata:true']);
279
        $resultRequest = curl_exec($curl);
280
        curl_close($curl);
281
282
        $arrKeys = [];
283
        $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

283
        $jsonData = json_decode(/** @scrutinizer ignore-type */ $resultRequest, true);
Loading history...
284
        $publicKeys = $jsonData['compute']['publicKeys']??[];
285
        foreach ($publicKeys as $keeData){
286
            $arrKeys[]= $keeData['keyData'];
287
        }
288
        $this->updateSSHKeys(implode(PHP_EOL, $arrKeys));
289
        $this->updateSshPassword();
290
        return $result;
291
    }
292
293
    /**
294
     * Возвращает строку XML для ответа о готовкности машины.
295
     * @param $containerId
296
     * @param $instanceId
297
     * @return string
298
     */
299
    private function getAzureXmlResponse($containerId, $instanceId):string
300
    {
301
        return '<Health>
302
  <GoalStateIncarnation>1</GoalStateIncarnation>
303
  <Container>
304
    <ContainerId>'.$containerId.'</ContainerId>
305
    <RoleInstanceList>
306
      <Role>
307
        <InstanceId>'.$instanceId.'</InstanceId>
308
        <Health>
309
          <State>Ready</State>
310
        </Health>
311
      </Role>
312
    </RoleInstanceList>
313
  </Container>
314
</Health>';
315
    }
316
}