Passed
Push — develop ( a04425...8843c6 )
by Nikolay
05:16 queued 14s
created

GoogleCloud   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 110
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 15
eloc 43
c 1
b 0
f 0
dl 0
loc 110
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A retrieveInstanceMetadata() 0 22 4
A extractPrimaryUser() 0 12 3
A containsGoogleDomain() 0 10 4
A provision() 0 27 3
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 MikoPBX\Core\System\SystemMessages;
23
use GuzzleHttp\Client;
24
use GuzzleHttp\Exception\GuzzleException;
25
26
class GoogleCloud extends CloudProvider
27
{
28
    private Client $client;
29
30
    public function __construct()
31
    {
32
        $this->client = new Client(['timeout' => self::HTTP_TIMEOUT]);
33
    }
34
35
    public const CloudID = 'GoogleCloud';
36
37
    /**
38
     * Performs the Google Cloud provisioning using the Metadata Service.
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
            return false;
47
        }
48
49
        SystemMessages::echoToTeletype(PHP_EOL);
50
51
        // Extract SSH keys and primary SSH user
52
        $sshKeys = $metadata['attributes']['ssh-keys'] ?? '';
53
        $adminUsername = $this->extractPrimaryUser($sshKeys);
54
55
        // Update machine name
56
        $hostname = $metadata['name'] ?? '';
57
        $this->updateHostName($hostname);
58
59
        // Update LAN settings with the external IP address
60
        $extIp = $metadata['networkInterfaces'][0]['accessConfigs'][0]['externalIp'] ?? '';
61
        $this->updateLanSettings($extIp);
62
63
        // Update SSH and WEB passwords using some unique identifier from the metadata
64
        $vmId = $metadata['id'];
65
        $this->updateSSHCredentials($adminUsername, $vmId);
66
        $this->updateWebPassword($vmId);
67
68
        return true;
69
    }
70
71
    /**
72
     * Retrieves Google Cloud instance metadata.
73
     *
74
     * @return array|null The instance metadata or null if retrieval failed.
75
     */
76
    private function retrieveInstanceMetadata(): ?array
77
    {
78
        try {
79
            $response = $this->client->request('GET', 'http://169.254.169.254/computeMetadata/v1/instance/', [
80
                'query' => ['recursive' => 'true'],
81
                'headers' => ['Metadata-Flavor' => 'Google']
82
            ]);
83
84
            if ($response->getStatusCode() == 200) {
85
                $metadata = json_decode($response->getBody()->getContents(), true);
86
                // Verify that metadata contains the 'serviceAccounts' with 'gserviceaccount' in any value
87
                if ($this->containsGoogleDomain($metadata)) {
88
                    return $metadata;
89
                } else {
90
                    SystemMessages::sysLogMsg(__CLASS__, "Metadata does not contain 'gserviceaccount' in any service account.");
91
                }
92
            }
93
        } catch (GuzzleException $e) {
94
            SystemMessages::sysLogMsg(__CLASS__, "Failed to retrieve Google Cloud instance metadata: " . $e->getMessage());
95
        }
96
97
        return null;
98
    }
99
100
    /**
101
     * Extracts the primary username from SSH keys.
102
     *
103
     * @param string $sshKeys SSH keys string from metadata.
104
     * @return string The primary SSH username extracted from SSH keys.
105
     */
106
    private function extractPrimaryUser(string $sshKeys): string
107
    {
108
        $lines = explode("\n", $sshKeys);
109
        foreach ($lines as $line) {
110
            $parts = explode(" ", $line);
111
            if (count($parts) > 2) {
112
                // The username usually follows the key, after the email or comment.
113
                $usernamePart = explode(":", $parts[2]);
114
                return $usernamePart[0];
115
            }
116
        }
117
        return 'root'; // Fallback username if no valid line was found
118
    }
119
120
    /**
121
     * Checks if the 'serviceAccounts' array contains the word 'gserviceaccount' in any value.
122
     *
123
     * @param array $metadata Metadata array from Google Cloud instance.
124
     * @return bool True if 'gserviceaccount' is found in any service account, false otherwise.
125
     */
126
    private function containsGoogleDomain(array $metadata): bool
127
    {
128
        if (isset($metadata['serviceAccounts'])) {
129
            foreach ($metadata['serviceAccounts'] as $account) {
130
                if (strpos($account['email'], 'gserviceaccount') !== false) {
131
                    return true;
132
                }
133
            }
134
        }
135
        return false;
136
    }
137
}