Интегрирующий агент PHP, S SH и s sh - PullRequest
1 голос
/ 10 февраля 2020

Я планирую написать скрипт PHP, который устанавливает соединение S SH. Я исследовал, как это сделать, и это выглядит наиболее многообещающим решением: https://github.com/phpseclib/phpseclib Моя единственная проблема - как справиться с тем фактом, что мой ключ S SH имеет парольную фразу, а я нет хочу вводить его каждый раз, когда я запускаю скрипт. Для каждого дня использования S SH у меня есть s sh -агент, работающий в фоновом режиме, и он настроен на использование pinentry. Это позволяет мне не вводить парольную фразу КАЖДЫЙ раз. Любые идеи относительно того, как я мог заставить PHP и s sh -агент говорить друг с другом? Единственное, что я могу понять, это то, что агент s sh устанавливает переменную окружения SSH_AUTH_SOCK, указывающую на файл сокета.

Хотя документация по phpseclib решает эту проблему, ее ответ глупый (просто введите фразу-пароль в коде): http://phpseclib.sourceforge.net/ssh/2.0/auth.html#encrsakey

ОБНОВЛЕНИЕ: я больше посмотрел на phpseclib и написал свой собственный простой класс-оболочку. Однако я не могу заставить его войти в систему ни через агент * sh, ни с помощью моего ключа RSA. Работает только аутентификация на основе пароля, в отличие от моего опыта входа в систему напрямую с помощью команды s sh. Вот мой код:

<?php
// src/Connection.php
declare(strict_types=1);
namespace MyNamespace\PhpSsh;

use phpseclib\System\SSH\Agent;
use phpseclib\Net\SSH2;
use phpseclib\Crypt\RSA;
use Exception;

class Connection
{
    private SSH2 $client;
    private string $host;
    private int $port;
    private string $username;

    /**
     * @param string $host
     * @param int $port
     * @param string $username
     * 
     * @return void
     */
    public function __construct(string $host, int $port,
        string $username)
    {
        $this->host = $host;
        $this->port = $port;
        $this->username = $username;
        $this->client = new SSH2($host, $port);
    }

    /**
     * @return bool
     */
    public function connectUsingAgent(): bool
    {
        $agent = new Agent();
        $agent->startSSHForwarding($this->client);
        return $this->client->login($this->username, $agent);
    }

    /**
     * @param string $key_path
     * @param string $passphrase
     * 
     * @return bool
     * @throws Exception
     */
    public function connectUsingKey(string $key_path, string $passphrase = ''): bool
    {
        if (!file_exists($key_path)) {
            throw new Exception(sprintf('Key file does not exist: %1$s', $key_path));
        }

        if (is_dir($key_path)) {
            throw new Exception(sprintf('Key path is a directory: %1$s', $key_path));
        }

        if (!is_readable($key_path)) {
            throw new Exception(sprintf('Key file is not readable: %1$s', $key_path));
        }

        $key = new RSA();

        if ($passphrase) {
            $key->setPassword($passphrase);
        }

        $key->loadKey(file_get_contents($key_path));

        return $this->client->login($this->username, $key);
    }

    /**
     * @param string $password
     * 
     * @return bool
     */
    public function connectUsingPassword(string $password): bool
    {
        return $this->client->login($this->username, $password);
    }

    /**
     * @return void
     */
    public function disconnect(): void
    {
        $this->client->disconnect();
    }

    /**
     * @param string $command
     * @param callable $callback
     * 
     * @return string|false
     */
    public function exec(string $command, callable $callback = null)
    {
        return $this->client->exec($command, $callback);
    }

    /**
     * @return string[]
     */
    public function getErrors(): array {
        return $this->client->getErrors();
    }
}

И:

<?php
    // test.php
    use MyNamespace\PhpSsh\Connection;

    require_once(__DIR__ . '/vendor/autoload.php');

    (function() {
        $host = '0.0.0.0'; // Fake, obviously
        $username = 'user'; // Fake, obviously
        $connection = new Connection($host, 22, $username);
        $connection_method = 'AGENT'; // or 'KEY', or 'PASSWORD'

        switch($connection_method) {
            case 'AGENT':
                $connected = $connection->connectUsingAgent();
                break;
            case 'KEY':
                $key_path = getenv( 'HOME' ) . '/.ssh/id_rsa.pub';
                $passphrase = trim(fgets(STDIN)); // Pass this in on command line via < key_passphrase.txt
                $connected = $connection->connectUsingKey($key_path, $passphrase);
                break;
            case 'PASSWORD':
            default:
                $password = trim(fgets(STDIN)); // Pass this in on command line via < password.txt
                $connected = $connection->connectUsingPassword($password);
                break;
        }

        if (!$connected) {
            fwrite(STDERR, "Failed to connect to server!" . PHP_EOL);
            $errors = implode(PHP_EOL, $connection->getErrors());
            fwrite(STDERR, $errors . PHP_EOL);
            exit(1);
        }

        $command = 'whoami';
        $result = $connection->exec($command);
        echo sprintf('Output of command "%1$s:"', $command) . PHP_EOL;
        echo $result . PHP_EOL;

        $command = 'pwd';
        $result = $connection->exec($command);
        echo sprintf('Output of command "%1$s:"', $command) . PHP_EOL;
        echo $result . PHP_EOL;

        $connection->disconnect();
    })();

Класс SSH2 имеет метод getErrors(), к сожалению, в моем случае он не регистрировался. Я должен был отладить класс. Я обнаружил, что, используя s sh -агент или передавая мой ключ, он всегда достигал этого места (https://github.com/phpseclib/phpseclib/blob/2.0.23/phpseclib/Net/SSH2.php#L2624):

<?php
// vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php: line 2624
extract(unpack('Ctype', $this->_string_shift($response, 1)));

switch ($type) {
    case NET_SSH2_MSG_USERAUTH_FAILURE:
        // either the login is bad or the server employs multi-factor authentication
        return false;
    case NET_SSH2_MSG_USERAUTH_SUCCESS:
        $this->bitmap |= self::MASK_LOGIN;
        return true;
}

Очевидно, что возвращаемый ответ типа NET_SSH2_MSG_USERAUTH_FAILURE. Я уверен, что в этом нет ничего плохого, поэтому в комментарии к коду, который означает, что хост (Digital Ocean) должен использовать многофакторную аутентификацию. Вот где я в тупике. Какие еще средства аутентификации мне не хватает? Вот где мое понимание S SH подводит меня.

Ответы [ 2 ]

1 голос
/ 27 февраля 2020

По neubert, мне нужно было добавить эту строку в Connection. php и я смог заставить агентскую аутентификацию работать:

$this->client->setPreferredAlgorithms(['hostkey' => ['ssh-rsa']]);

Я до сих пор не могу получить ключ на основе аутентификации, но мне все равно.

1 голос
/ 11 февраля 2020

phpseclib поддерживает S SH Агент. например.

use phpseclib\Net\SSH2;
use phpseclib\System\SSH\Agent;

$agent = new Agent;

$ssh = new SSH2('localhost');
if (!$ssh->login('username', $agent)) {
    throw new \Exception('Login failed');
}

Обновление с вашими последними изменениями

ОБНОВЛЕНИЕ: я больше посмотрел на phpseclib и написал свой собственный простой класс-обертка Однако я не могу заставить его войти в систему ни через агент s sh, ни с помощью моего ключа RSA. Работает только аутентификация на основе пароля, в отличие от моего опыта входа в систему напрямую с помощью команды s sh.

Как выглядят ваши журналы S SH как с аутентификацией агента, так и с прямой публикацией c ключ аутентификации? Вы можете получить журналы S SH, выполнив define('NET_SSH2_LOGGING', 2) вверху и затем echo $ssh->getLog() после сбоя аутентификации.

...