fix(proxmox): harden console sessions and firewall rollout safety

Reduce security and reliability risk across the Proxmox extension by removing password exposure from Livewire state, hardening console auth/cookie handling, and improving sanitized error paths. Improve firewall and router performance by adding safer lazy loading, bounded pagination, cross-server compatibility guards, and lower-overhead API workflows.

Made-with: Cursor
This commit is contained in:
2026-03-28 19:34:46 -04:00
parent 9f996035db
commit 68043f0416
39 changed files with 1106 additions and 5013 deletions

View File

@@ -23,14 +23,8 @@ class CloudInit extends Component
'l26',
'solaris',
];
private const MANAGEMENT_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCBeSz45D52fvy4EKPUvI4Upq2t1Ae+MJxB4Lla17f7kHFmoDCJvHfXUCxNv+Tr7FtbCWCdZ2yj8jvS8TNuJ+dzUORbRMgJcokmMLj486RYT7luRHPi+kU5CCv+yIOTNl308wTNmY12rmj+9/81gJA+ipll6UlNTh4T//koSwtcu0mw8ElyV8vjz+s+2vTbPRP+TMPGsFATyJjcjvFvtISW/TT0MuUkbk/bLpv8iurMjFSORa2/lnnMfnznSEL3LQC7CrWvpiCV7mhPn4ZLR/MksvzNnjjKzJHw2u5b8NmiztCfrPUTE/LDNwXh6WtIgqXt7BcjRUT1lR+MMLkhb1lf rsa-key-20240918';
public string $ciUser = '';
public string $password = '';
public string $password_confirmation = '';
public string $dnsDomain = '';
public array $dnsServers = [];
@@ -128,8 +122,11 @@ class CloudInit extends Component
$normalizedKeys = array_filter(array_map('trim', $this->sshKeys));
$encodedKeys = array_map('rawurlencode', $normalizedKeys);
$encodedManagementKey = rawurlencode(self::MANAGEMENT_KEY);
$allKeys = array_merge($encodedKeys, [$encodedManagementKey]);
$allKeys = $encodedKeys;
$managementKey = $this->managementSshKey();
if ($managementKey !== '' && !in_array($managementKey, $normalizedKeys, true)) {
$allKeys[] = rawurlencode($managementKey);
}
$payload['sshkeys'] = implode('%0A', $allKeys);
foreach ($this->networks as $network) {
@@ -190,7 +187,8 @@ class CloudInit extends Component
$this->notify(implode(' ', $messageParts), $messageLevel);
} catch (\Exception $e) {
$this->notify('Failed to save cloud-init settings: ' . $e->getMessage(), 'info');
report($e);
$this->notify('Failed to save cloud-init settings. Please try again.', 'error');
}
}
@@ -235,7 +233,8 @@ class CloudInit extends Component
];
}
} catch (\Exception $e) {
$this->notify('Unable to load cloud-init data: ' . $e->getMessage(), 'error');
report($e);
$this->notify('Unable to load cloud-init data. Please refresh and try again.', 'error');
}
}
@@ -260,10 +259,25 @@ class CloudInit extends Component
return array_values(array_filter($keys, function ($key) {
$normalized = preg_replace('/\s+/', ' ', trim($key));
return $normalized !== self::MANAGEMENT_KEY;
$managementKey = $this->managementSshKey();
if ($managementKey === '') {
return true;
}
return $normalized !== $managementKey;
}));
}
protected function managementSshKey(): string
{
$configured = config('services.proxmox.management_ssh_key');
if (is_string($configured) && trim($configured) !== '') {
return trim($configured);
}
return trim((string) env('PROXMOX_MANAGEMENT_SSH_KEY', ''));
}
protected function extractNetworkConfigs(array $config): array
{
$networks = [];
@@ -380,16 +394,12 @@ class CloudInit extends Component
public function startEditingPassword(): void
{
$this->isEditingPassword = true;
$this->password = '';
$this->password_confirmation = '';
$this->passwordError = '';
}
public function cancelEditingPassword(): void
{
$this->isEditingPassword = false;
$this->password = '';
$this->password_confirmation = '';
$this->passwordError = '';
}
@@ -438,12 +448,12 @@ class CloudInit extends Component
$this->cancelEditingNetwork();
}
protected function validatePasswordFields(): bool
protected function validatePasswordFields(string $password, string $passwordConfirmation): bool
{
$validator = Validator::make(
[
'password' => $this->password,
'password_confirmation' => $this->password_confirmation,
'password' => $password,
'password_confirmation' => $passwordConfirmation,
],
[
'password' => 'required|string|min:8|confirmed',
@@ -459,33 +469,40 @@ class CloudInit extends Component
return true;
}
public function updatePassword(): void
protected function closePasswordEditor(): void
{
if (!$this->validatePasswordFields()) {
$this->isEditingPassword = false;
$this->passwordError = '';
}
public function updatePassword(string $password = '', string $passwordConfirmation = ''): void
{
if (!$this->validatePasswordFields($password, $passwordConfirmation)) {
return;
}
try {
$this->server->password = $this->password;
$this->server->password = $password;
$this->server->save();
$this->server->refresh();
$this->notify('Password saved to database successfully.', 'success');
$this->cancelEditingPassword();
$this->closePasswordEditor();
} catch (\Exception $e) {
$this->passwordError = 'Failed to save password: ' . $e->getMessage();
report($e);
$this->passwordError = 'Failed to save password. Please try again.';
$this->notify('Failed to save password', 'error');
}
}
public function applyPassword(): void
public function applyPassword(string $password = '', string $passwordConfirmation = ''): void
{
if (!$this->validatePasswordFields()) {
if (!$this->validatePasswordFields($password, $passwordConfirmation)) {
return;
}
try {
$this->server->password = $this->password;
$this->server->password = $password;
$this->server->save();
$this->server->refresh();
@@ -494,14 +511,15 @@ class CloudInit extends Component
'/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config',
'put',
[
'cipassword' => $this->password,
'cipassword' => $password,
]
);
$this->notify('Password applied. Reboot the server for cloud-init to set the new password.', 'success');
$this->cancelEditingPassword();
$this->closePasswordEditor();
} catch (\Exception $e) {
$this->passwordError = 'Failed to apply password: ' . $e->getMessage();
report($e);
$this->passwordError = 'Failed to apply password. Please try again.';
$this->notify('Failed to apply password to Proxmox', 'error');
}
}