53 lines
2.1 KiB
PHP
53 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Ssh;
|
|
|
|
use phpseclib3\Crypt\EC;
|
|
|
|
/**
|
|
* The SSH identity this installation reaches its hosts with, minted in PHP.
|
|
*
|
|
* Same reasoning as App\Services\Wireguard\Keypair, one layer up: the console
|
|
* runs as www-data inside a container, shelling out to `ssh-keygen` would need
|
|
* a binary and a writable path that only exist by accident, and a key written
|
|
* to a temp file is a private key on a disk somebody has to remember to shred.
|
|
* Generated here, the private half goes straight into the vault and never
|
|
* touches the filesystem at all.
|
|
*
|
|
* Built on phpseclib rather than a hand-rolled `openssh-key-v1` frame, and not
|
|
* for convenience: PhpseclibRemoteShell::connectWithKey() hands the stored text
|
|
* to `PublicKeyLoader::load()`. Minting with the same library is what makes
|
|
* "this is a valid OpenSSH key" the same statement as "this logs in" — a
|
|
* self-written frame that merely looked right would be found out at the first
|
|
* host takeover, after the root password had already been scrubbed.
|
|
*
|
|
* Ed25519 because a modern OpenSSH accepts it everywhere, the key is short
|
|
* enough to read out over a phone, and there is no key size to get wrong.
|
|
*/
|
|
final readonly class Keypair
|
|
{
|
|
/**
|
|
* What the public half is labelled with in `authorized_keys`.
|
|
*
|
|
* Fixed rather than derived from the installation: this string lands on
|
|
* every host's root account, and an operator reading that file wants to see
|
|
* WHO the key belongs to — not the hostname of whichever machine the panel
|
|
* happened to run on when it was minted.
|
|
*/
|
|
public const COMMENT = 'clupilot';
|
|
|
|
public function __construct(public string $privateKey, public string $publicKey) {}
|
|
|
|
public static function generate(): self
|
|
{
|
|
$key = EC::createKey('Ed25519');
|
|
|
|
return new self(
|
|
privateKey: $key->toString('OpenSSH'),
|
|
// One line, `ssh-ed25519 AAAA… clupilot`, which is the whole of
|
|
// what EstablishSshTrust appends to a host's authorized_keys.
|
|
publicKey: $key->getPublicKey()->toString('OpenSSH', ['comment' => self::COMMENT]),
|
|
);
|
|
}
|
|
}
|