Passed
Push — develop ( 57ce40...14709f )
by Nikolay
05:28
created

YandexCloud::extractUserNameFromSshKeys()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 8
rs 10
cc 2
nc 2
nop 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2024 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\CloudProvisioning;
21
22
use GuzzleHttp\Client;
23
use GuzzleHttp\Exception\GuzzleException;
24
use MikoPBX\Core\System\SystemMessages;
25
26
class YandexCloud extends CloudProvider
27
{
28
    public const CloudID = 'YandexCloud';
29
30
    private Client $client;
31
32
    public function __construct()
33
    {
34
        $this->client = new Client(['timeout' => self::HTTP_TIMEOUT]);
35
    }
36
37
    /**
38
     * Performs the Yandex Cloud provisioning.
39
     *
40
     * @return bool True if the provisioning was successful, false otherwise.
41
     */
42
    public function provision(): bool
43
    {
44
        $metadata = $this->retrieveInstanceMetadata();
45
        if ($metadata === null || empty($metadata['id'])) {
46
            // If metadata is null ot machine id is unknown, do not proceed with provisioning.
47
            return false;
48
        }
49
50
        SystemMessages::echoToTeletype(PHP_EOL);
51
52
        $sshKeys = $metadata['attributes']['ssh-keys']??'';
53
54
        // Extract username
55
        $username = $this->extractUserNameFromSshKeys($sshKeys);
56
57
        // Update SSH keys, if available
58
        $this->updateSSHKeys($sshKeys);
59
60
        // Update machine name
61
        $hostname = $metadata['name'] ?? '';
62
        $this->updateHostName($hostname);
63
64
        // Update LAN settings with the external IP address
65
        $extIp = $metadata['networkInterfaces'][0]['accessConfigs'][0]['externalIp'] ?? '';
66
        $this->updateLanSettings($extIp);
67
68
        // Update SSH and WEB passwords using some unique identifier from the metadata
69
        $vmId = $metadata['id'];
70
        $this->updateSSHCredentials($username ?? 'yc-user', $vmId);
71
        $this->updateWebPassword($vmId);
72
73
        return true;
74
    }
75
76
    /**
77
     * Retrieves Yandex instance metadata.
78
     *
79
     * @return array|null The instance metadata or null if retrieval failed.
80
     *
81
     */
82
    private function retrieveInstanceMetadata(): ?array
83
    {
84
        try {
85
            $response = $this->client->request('GET', 'http://169.254.169.254/computeMetadata/v1/instance/', [
86
                'query' => ['recursive' => 'true', 'alt' => 'json'],
87
                'headers' => ['Metadata-Flavor' => 'Google']
88
            ]);
89
90
            if ($response->getStatusCode() == 200) {
91
                $metadata = json_decode($response->getBody()->getContents(), true);
92
                // Verify that metadata not contains the 'serviceAccounts' with 'gserviceaccount' in any value
93
                if ($this->notContainsGoogleDomain($metadata)) {
94
                    return $metadata;
95
                } else {
96
                    SystemMessages::sysLogMsg(__CLASS__, "Metadata contain 'gserviceaccount' in service account.");
97
                }
98
            }
99
        } catch (GuzzleException $e) {
100
            SystemMessages::sysLogMsg(__CLASS__, "Failed to retrieve Yandex Cloud instance metadata: " . $e->getMessage());
101
        }
102
103
        return null;
104
    }
105
106
    /**
107
     * Extracts the username from user-data script.
108
     *
109
     * @param string $sshKeys SSH keys sting.
110
     * @return string|null Extracted username or null if not found.
111
     */
112
    private function extractUserNameFromSshKeys(string $sshKeys): ?string
113
    {
114
        $parts = explode(':', $sshKeys);
115
        $username = $parts[0];
116
        if (strlen($username) >= 3) {
117
            return $username;
118
        }
119
        return null; // Return null if no username found
120
    }
121
122
    /**
123
     * Checks if the 'serviceAccounts' array contains the word 'gserviceaccount' in any value.
124
     *
125
     * @param array $metadata Metadata array from Google Cloud instance.
126
     * @return bool False if 'gserviceaccount' is found in any service account, true otherwise.
127
     */
128
    private function notContainsGoogleDomain(array $metadata): bool
129
    {
130
        if (isset($metadata['serviceAccounts'])) {
131
            foreach ($metadata['serviceAccounts'] as $account) {
132
                if (strpos($account['email'], 'gserviceaccount') !== false) {
133
                    return false;
134
                }
135
            }
136
        }
137
        return true;
138
    }
139
}