Некоторые возможности ssh в golang

от автора

Создать ssh-сервер на Go можно при помощи модуля golang.org/x/crypto/ssh.

А при помощи пакета github.com/gliderlabs/ssh можно разработать ssh-сервер легко и быстро. Ssh подразумевает не только доступ к оболочке(shell), но и прочие возможности: файловый сервер(sftp), проброс портов.

Репозиторий проекта содержит минимальный пример, выводящий строку «Hello world» любому подключенному ssh-клиенту.

 package main   import (      "github.com/gliderlabs/ssh"      "io"      "log"  )   func main() {      ssh.Handle(func(s ssh.Session) {          io.WriteString(s, "Hello world\n")      })         log.Fatal(ssh.ListenAndServe(":2222", nil))  }

Терминал

Полноценный терминальный эмулятор можно реализовать при помощи модуля golang.org/x/term.

Упрощенно обработчик будет выглядеть вот так:

import (   ...   terminal "golang.org/x/term" )  func sessionHandler(s gssh.Session) { defer s.Close() if s.RawCommand() != "" { io.WriteString(s, "raw commands are not supported") return }          // создаем терминал term := terminal.NewTerminal(s, fmt.Sprintf("/%s/ > ", s.User()))      // добавляем обработку pty-request  pty, winCh, isPty := s.Pty() if isPty { _ = pty go func() {             // реагируем на изменение размеров терминала for chInfo := range winCh { _ = term.SetSize(chInfo.Width, chInfo.Height) } }() }  for {         // считываем ввод пользователя line, err := term.ReadLine() if err == io.EOF { _, _ = io.WriteString(s, "EOF.\n") break }          // обработаем результат result = processInput(line)                // выведем в терминал         io.WriteString(term, result) } }

Обрабатываем команды, пакет flag

Пользователь может вводить команды и их необходимо обработать и исполнить. По-началу я интегрировал github.com/spf13/cobra, но что-то пошло не так — повторный запуск rootCmd.Execute() приводил к неожиданным результатам и ошибкам. После коротких раздумий от cobra решено было отказаться и обойтись средствами попроще.

Можно воспользоваться стандартным пакетом flag, предварительно обработав пользовательский ввод лексером github.com/google/shlex:

    import (       ...       "github.com/google/shlex"     )      ...SNIP..     // скармливаем лексеру     args, err := shlex.Split(line)     if err != nil {         io.WriteString(term,             fmt.Errorf("splitting args: %w\n", err).Error())         continue     }     if len(args) == 0 {         continue     }     cmdName := args[0]     args = args[1:]     ..      // теперь парсим флаги     flagCmd := flag.NewFlagSet("foo", flag.ContinueOnError) enableP := flagCmd.Bool("enable", false, "enable") nameP := flagCmd.String("name", "", "name") flagCmd.SetOutput(term) err := flagCmd.Parse(args) if err != nil { return fmt.Errorf("error parsing flags: %w", err) }      // и выполнение нужных действий io.WriteString(term, fmt.Sprintf("parsed flags:\n")) io.WriteString(term, fmt.Sprintf("  - enable: \t%v\n", *enableP)) io.WriteString(term, fmt.Sprintf("  - name: \t%v\n", *nameP)) io.WriteString(term, fmt.Sprintf("  - tail: \t%v\n", flagCmd.Args()))      ...SNIP..

Добавляем аутентификацию

Было бы глупо игнорировать такую тему как аутентификция пользователя ssh-сессии. Очень заманчива идея в бекенд сервисе дать пользователю возможность управлять разрешенными публичными ключами.

Пример обработчика аутентификации по публичному ключу. Пример простой — проверяется совпадение публичного ключа сессии и ключа из файла. Имя пользователя не проверяется никак, хотя его можно получить вызовом ctx.User().

import (   ...   gssh "github.com/gliderlabs/ssh" )  func pubkeyAuth(ctx gssh.Context, key gssh.PublicKey) bool { mykey, err := os.ReadFile("./mykey.pub") if err != nil { log.Printf("reading file: %w\n", err) return false } pk, _, _, _, err := gssh.ParseAuthorizedKey(mykey) if err != nil { log.Printf("parse auth key: %\n", err) return false } if !bytes.Equal(key.Marshal(), pk.Marshal()) { return false } return true }

Аналогично можно сделать и аутентификацию по паролю.

Все вместе

Соединяя все вместе получаем следующее: ssh-server с аутентификацией по паролю и по публичному ключу, с поддержкой pty-терминала и обработкой команд стандартным пакетом flag.

Исходный код проекта: https://github.com/shabunin/sshebra

Проект маленький — всего три go файла:

  • sshebra/sshebra.go — обработчик ssh-сессии и парсер команд

  • commands/command.go — определение интерфейса и примеры команд

  • main.go — пример использования

В примере используется три команды: whoami, flag и exit.

Прочие проекты

Вдохновили меня на работу с ssh следующие чаты поверх ssh:

И недавно я наткнулся на проект https://github.com/charmbracelet/wish. Проверить его возможности и посмотреть на терминальный интерфейс git-сервера можно прямо из своего терминала: ssh git.charm.sh.

Прочие возможности ssh

Текстовый интерфейс может быть и удобным и красивым. Однако любим мы ssh за гораздо более полезные вещи: пробросы портов, трансфер файлов. Для данной статьи материала по ним не хватило. Примеры же можно посмотреть здесь.


ссылка на оригинал статьи https://habr.com/ru/articles/788094/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *