120 lines
3.6 KiB
PHP
120 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Modals;
|
|
|
|
use App\Models\AuditEvent;
|
|
use App\Models\Server;
|
|
use App\Services\FleetService;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use LivewireUI\Modal\ModalComponent;
|
|
use phpseclib3\Crypt\EC;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Form modal: paste an SSH public key, append it to the server's authorized_keys
|
|
* over SSH (real control), write an AuditEvent, and tell the page to reload.
|
|
*/
|
|
class AddSshKey extends ModalComponent
|
|
{
|
|
public int $serverId;
|
|
|
|
public string $keyName = '';
|
|
|
|
public string $publicKey = '';
|
|
|
|
public ?string $generatedPrivate = null;
|
|
|
|
public ?string $error = null;
|
|
|
|
public function mount(int $serverId): void
|
|
{
|
|
// Installing an authorized_keys entry over SSH is a fleet-admin action.
|
|
abort_unless(auth()->user()?->can('manage-fleet'), 403);
|
|
|
|
$this->serverId = $serverId;
|
|
}
|
|
|
|
public static function modalMaxWidth(): string
|
|
{
|
|
return 'lg';
|
|
}
|
|
|
|
/** Generate a fresh ed25519 keypair: the public key is installed on save,
|
|
* the private key is shown once for the operator to store. */
|
|
public function generate(): void
|
|
{
|
|
$this->error = null;
|
|
$key = EC::createKey('ed25519');
|
|
// The comment is what identifies the key in the server's list — use the operator's name.
|
|
$this->publicKey = $key->getPublicKey()->toString('OpenSSH', ['comment' => $this->keyComment() ?: 'clusev-key']);
|
|
$this->generatedPrivate = (string) $key->toString('OpenSSH');
|
|
}
|
|
|
|
/** Sanitised key label, safe as an authorized_keys comment (single line, ≤64 chars), or '' if none. */
|
|
private function keyComment(): string
|
|
{
|
|
$name = preg_replace('/[\x00-\x1F\x7F]+/', ' ', $this->keyName) ?? '';
|
|
$name = trim(preg_replace('/\s+/', ' ', $name) ?? '');
|
|
|
|
return mb_substr($name, 0, 64);
|
|
}
|
|
|
|
/** Set the OpenSSH comment of a public-key line to $comment (only when a name was given). */
|
|
private function withComment(string $pub, string $comment): string
|
|
{
|
|
if ($comment === '') {
|
|
return $pub;
|
|
}
|
|
$parts = preg_split('/\s+/', trim($pub), 3);
|
|
if (count($parts) < 2) {
|
|
return $pub; // not a recognisable key line — let the SSH layer reject it
|
|
}
|
|
|
|
return $parts[0].' '.$parts[1].' '.$comment;
|
|
}
|
|
|
|
public function save(FleetService $fleet): void
|
|
{
|
|
// Re-gate on save: refuse a demoted user / hand-crafted /livewire/update after mount.
|
|
abort_unless(auth()->user()?->can('manage-fleet'), 403);
|
|
|
|
$this->error = null;
|
|
|
|
$server = Server::find($this->serverId);
|
|
if (! $server) {
|
|
$this->error = __('common.server_not_found');
|
|
|
|
return;
|
|
}
|
|
|
|
$name = $this->keyComment();
|
|
|
|
try {
|
|
// Force the key's comment to the operator's name so it is identifiable in the list.
|
|
$fleet->addAuthorizedKey($server, $this->withComment($this->publicKey, $name));
|
|
} catch (Throwable $e) {
|
|
$this->error = $e->getMessage();
|
|
|
|
return;
|
|
}
|
|
|
|
AuditEvent::create([
|
|
'user_id' => Auth::id(),
|
|
'server_id' => $server->id,
|
|
'actor' => Auth::user()?->name ?? 'system',
|
|
'action' => 'ssh_key.add',
|
|
'target' => $name !== '' ? $server->name.' · '.$name : $server->name,
|
|
'ip' => request()->ip(),
|
|
]);
|
|
|
|
$this->dispatch('keyChanged');
|
|
$this->dispatch('notify', message: __('modals.add_ssh_key.notify_added'));
|
|
$this->closeModal();
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.modals.add-ssh-key');
|
|
}
|
|
}
|