diff --git a/app/Livewire/Admin/Vpn.php b/app/Livewire/Admin/Vpn.php index 2fc30c7..4b85c19 100644 --- a/app/Livewire/Admin/Vpn.php +++ b/app/Livewire/Admin/Vpn.php @@ -222,6 +222,50 @@ class Vpn extends Component $this->dispatch('notify', message: __('vpn.deleted')); } + /** + * Replace an access's keypair. + * + * The way back for an access whose config was never stored: nobody can hand + * out a private key that no longer exists, so the honest option is a new + * one. The old key stops working the moment the hub is updated, which is + * also what makes this the right tool for a lost or leaked device. + */ + public function reissue(string $uuid): void + { + $peer = VpnPeer::query()->where('uuid', $uuid)->first(); + if ($peer === null) { + return; + } + $this->authorize('update', $peer); + + if ($peer->kind !== VpnPeer::KIND_STAFF) { + $this->dispatch('notify', message: __('vpn.reissue_staff_only')); + + return; + } + + $keypair = Keypair::generate(); + $oldKey = $peer->public_key; + $config = $this->clientConfig($keypair, $peer->allowed_ip); + + $peer->forceFill([ + 'public_key' => $keypair->publicKey, + 'present' => false, + 'config_secret' => $peer->hasStoredConfig() ? ConfigVault::encrypt($config) : null, + ])->save(); + + // Old key off the hub first, then the new one on: the other order would + // briefly leave two peers claiming the same tunnel address. + ApplyVpnPeer::dispatch($oldKey, null, false); + ApplyVpnPeer::dispatch($peer->public_key, $peer->allowed_ip, true); + + $this->dismissConfig(); + $this->configToken = ConfigHandoff::put($config); + $this->newConfigName = $peer->name; + + $this->dispatch('notify', message: __('vpn.reissued')); + } + public function dismissConfig(): void { ConfigHandoff::forget($this->configToken); diff --git a/lang/de/vpn.php b/lang/de/vpn.php index 68789ed..1d49770 100644 --- a/lang/de/vpn.php +++ b/lang/de/vpn.php @@ -76,6 +76,12 @@ return [ 'too_many_attempts' => 'Zu viele Versuche. Bitte in :seconds Sekunden erneut versuchen.', 'config_unreadable' => 'Die gespeicherte Konfiguration lässt sich nicht entschlüsseln. Bitte den Zugang neu ausstellen.', + 'no_stored_config' => 'Für diesen Zugang wurde die Konfiguration nicht gespeichert — der private Schlüssel existiert nur noch auf Ihrem Gerät. Über „Neu ausstellen" bekommen Sie eine neue.', + 'reissue' => 'Neu ausstellen', + 'reissue_confirm' => 'Für :name ein neues Schlüsselpaar erzeugen? Die bisherige Konfiguration wird sofort ungültig.', + 'reissued' => 'Neuer Schlüssel erzeugt — die alte Konfiguration gilt nicht mehr.', + 'reissue_staff_only' => 'Host-Zugänge werden über die Host-Verwaltung erneuert, nicht hier.', + 'hub' => 'Hub', 'endpoint' => 'Endpunkt', 'hub_key' => 'Public Key', diff --git a/lang/en/vpn.php b/lang/en/vpn.php index 8be3b62..8e82ef4 100644 --- a/lang/en/vpn.php +++ b/lang/en/vpn.php @@ -76,6 +76,12 @@ return [ 'too_many_attempts' => 'Too many attempts. Try again in :seconds seconds.', 'config_unreadable' => 'The stored configuration cannot be decrypted. Please re-issue the access.', + 'no_stored_config' => 'This access was created without storing its configuration — the private key only exists on your device. Use "Re-issue" to get a new one.', + 'reissue' => 'Re-issue', + 'reissue_confirm' => 'Generate a new keypair for :name? The current configuration stops working immediately.', + 'reissued' => 'New key issued — the old configuration no longer works.', + 'reissue_staff_only' => 'Host accesses are renewed through host management, not here.', + 'hub' => 'Hub', 'endpoint' => 'Endpoint', 'hub_key' => 'Public key', diff --git a/resources/views/components/ui/icon.blade.php b/resources/views/components/ui/icon.blade.php index 850ee8e..95d937e 100644 --- a/resources/views/components/ui/icon.blade.php +++ b/resources/views/components/ui/icon.blade.php @@ -18,6 +18,7 @@ 'download' => '', 'alert-triangle' => '', 'check' => '', + 'refresh' => '', 'bell' => '', 'calendar' => '', 'qr-code' => '', diff --git a/resources/views/livewire/admin/vpn.blade.php b/resources/views/livewire/admin/vpn.blade.php index 5493bc1..4eca8c6 100644 --- a/resources/views/livewire/admin/vpn.blade.php +++ b/resources/views/livewire/admin/vpn.blade.php @@ -138,6 +138,14 @@
+ {{-- A stored config can be fetched again; without one there is + nothing to hand out, and saying so beats an absent button. --}} + @if (! $peer->hasStoredConfig() && $peer->kind === \App\Models\VpnPeer::KIND_STAFF && $peer->user_id === auth()->id()) + + + + @endif @can('downloadConfig', $peer) @endcan + @can('update', $peer) + @if ($peer->kind === \App\Models\VpnPeer::KIND_STAFF) + + @endif + @endcan @can('delete', $peer)