Skip to content

Commit

Permalink
refactor: Wip key removal
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen committed Jul 23, 2024
1 parent 8d490a0 commit 04aeccd
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 90 deletions.

This file was deleted.

12 changes: 0 additions & 12 deletions app/Services/RemoveSSHKey/Contracts/SSHClientInterface.php

This file was deleted.

12 changes: 0 additions & 12 deletions app/Services/RemoveSSHKey/Contracts/SSHKeyProviderInterface.php

This file was deleted.

156 changes: 104 additions & 52 deletions app/Services/RemoveSSHKey/RemoveSSHKeyService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,99 +4,151 @@

namespace App\Services\RemoveSSHKey;

use App\Exceptions\SSHConnectionException;
use App\Facades\ServerConnection;
use App\Mail\RemoteServers\FailedToRemoveKey;
use App\Mail\RemoteServers\SuccessfullyRemovedKey;
use App\Models\RemoteServer;
use App\Services\RemoveSSHKey\Contracts\KeyRemovalNotifierInterface;
use App\Services\RemoveSSHKey\Contracts\SSHClientInterface;
use App\Services\RemoveSSHKey\Contracts\SSHKeyProviderInterface;
use Illuminate\Support\Facades\Log;
use App\Support\ServerConnection\Connection;
use App\Support\ServerConnection\Exceptions\ConnectionException;
use Illuminate\Support\Facades\Mail;
use Psr\Log\LoggerInterface;

readonly class RemoveSSHKeyService
class RemoveSSHKeyService
{
public function __construct(
private SSHClientInterface $sshClient,
private KeyRemovalNotifierInterface $keyRemovalNotifier,
private SSHKeyProviderInterface $sshKeyProvider
) {}
private LoggerInterface $logger;

public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}

/**
* Handle the SSH key removal process for a given remote server.
*
* This method orchestrates the process of removing an SSH key from a remote server,
* including establishing a connection, executing the removal command, and notifying
* the user of the result.
*
* @param RemoteServer $remoteServer The remote server to remove the SSH key from
*/
public function handle(RemoteServer $remoteServer): void
{
Log::info('Initiating SSH key removal process.', ['server_id' => $remoteServer->getAttribute('id')]);
$this->logger->info("Initiating SSH key removal for server: {$remoteServer->label}");

try {
$this->connectToServer($remoteServer);
$this->removeKeyFromServer($remoteServer);
$this->notifySuccess($remoteServer);
} catch (SSHConnectionException $e) {
$connection = $this->establishConnection($remoteServer);
$publicKey = $this->getSSHPublicKey();

if (!$this->determineKeyExistence($connection, $publicKey)) {
$this->logger->info("SSH key does not exist on server: {$remoteServer->label}");
$this->handleSuccessfulRemoval($remoteServer);
return;
}

$result = $this->executeRemovalCommand($connection, $publicKey);
$this->processRemovalResult($remoteServer, $result);

} catch (ConnectionException $e) {
$this->handleConnectionFailure($remoteServer, $e);
}
}

/**
* Establish an SSH connection to the remote server.
* Establish a connection to the remote server.
*
* @param RemoteServer $remoteServer The remote server to connect to
* @return Connection The connection instance.
*
* @throws SSHConnectionException If unable to connect to the remote server
* @throws ConnectionException
*/
private function connectToServer(RemoteServer $remoteServer): void
private function establishConnection(RemoteServer $remoteServer): Connection
{
$privateKey = $this->sshKeyProvider->getPrivateKey();

if (! $this->sshClient->connect(
$remoteServer->getAttribute('ip_address'),
$remoteServer->getAttribute('port'),
$remoteServer->getAttribute('username'),
$privateKey
)) {
throw new SSHConnectionException('Failed to connect to remote server');
}
$this->logger->debug("Attempting to connect to server: {$remoteServer->label}");

return ServerConnection::connectFromModel($remoteServer)->establish();
}

/**
* Get the SSH public key.
*/
private function getSSHPublicKey(): string
{
//TODO: Replace with a ServerConnectionManager readonly implementation that gets the public key
return get_ssh_public_key();
}

/**
* Check if the SSH public key exists on the remote server.
*/
private function determineKeyExistence(Connection $connection, string $publicKey): bool
{
$command = sprintf("grep -q '%s' ~/.ssh/authorized_keys", preg_quote($publicKey, '/'));
$this->logger->debug("Checking if SSH key exists with command: {$command}");

$result = $connection->run($command);

return $result === '';
}

/**
* Remove the SSH key from the remote server.
* Execute the SSH key removal command on the remote server.
*
* @param RemoteServer $remoteServer The remote server to remove the key from
* @return string The result of the command execution
*/
private function removeKeyFromServer(RemoteServer $remoteServer): void
private function executeRemovalCommand(Connection $connection, string $publicKey): string
{
$publicKey = $this->sshKeyProvider->getPublicKey();
$command = sprintf("sed -i -e '/^%s/d' ~/.ssh/authorized_keys", preg_quote($publicKey, '/'));
$this->logger->debug("Executing removal command: {$command}");

$this->sshClient->executeCommand($command);
return $connection->run($command);
}

Log::info('SSH key removed from server.', ['server_id' => $remoteServer->getAttribute('id')]);
/**
* Process the result of the key removal attempt.
*/
private function processRemovalResult(RemoteServer $remoteServer, string $result): void
{
if ($this->isRemovalSuccessful($result)) {
$this->handleSuccessfulRemoval($remoteServer);
} else {
$this->handleFailedRemoval($remoteServer, $result);
}
}

/**
* Notify the user of successful key removal.
*
* @param RemoteServer $remoteServer The remote server the key was removed from
* Determine if the key removal was successful based on the command result.
*/
private function notifySuccess(RemoteServer $remoteServer): void
private function isRemovalSuccessful(string $result): bool
{
$this->keyRemovalNotifier->notifySuccess($remoteServer);
Log::info('User notified of successful key removal.', ['server_id' => $remoteServer->getAttribute('id')]);
return $result === '' || str_contains($result, 'Successfully removed');
}

/**
* Handle and log connection failures, and notify the user.
*
* @param RemoteServer $remoteServer The remote server that failed to connect
* @param SSHConnectionException $sshConnectionException The exception that occurred
* Handle successful key removal.
*/
private function handleSuccessfulRemoval(RemoteServer $remoteServer): void
{
$this->logger->info("Successfully removed SSH key from server: {$remoteServer->label}");

Mail::to($remoteServer->user)->queue(new SuccessfullyRemovedKey($remoteServer));
}

/**
* Handle failed key removal.
*/
private function handleFailedRemoval(RemoteServer $remoteServer, string $result): void
{
$this->logger->error("Failed to remove SSH key from server: {$remoteServer->label}. Result: {$result}");

Mail::to($remoteServer->user)->queue(new FailedToRemoveKey($remoteServer));
}

/**
* Handle connection failure.
*/
private function handleConnectionFailure(RemoteServer $remoteServer, SSHConnectionException $sshConnectionException): void
private function handleConnectionFailure(RemoteServer $remoteServer, ConnectionException $exception): void
{
Log::error('Failed to connect to remote server for key removal.', [
'server_id' => $remoteServer->getAttribute('id'),
'error' => $sshConnectionException->getMessage(),
]);
$this->logger->error("Failed to connect to server: {$remoteServer->label}. Error: {$exception->getMessage()}");

$this->keyRemovalNotifier->notifyFailure($remoteServer, $sshConnectionException->getMessage());
Mail::to($remoteServer->user)->queue(new FailedToRemoveKey($remoteServer));
}
}
4 changes: 4 additions & 0 deletions app/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ function ssh_keys_exist(): bool
}

/**
* @deprecated Please use the ServerConnectionManager static implementation.
*
* Get the contents of the SSH public key.
*
* @return string The contents of the SSH public key.
Expand All @@ -35,6 +37,8 @@ function get_ssh_public_key(): string
}

/**
* @deprecated Please use the ServerConnectionManager static implementation.
*
* Get the contents of the SSH private key.
*
* @return string The contents of the SSH private key.
Expand Down

0 comments on commit 04aeccd

Please sign in to comment.