Некоторые возможности ssh в golang
- среда, 24 января 2024 г. в 00:00:19
Создать 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)
}
}
Пользователь может вводить команды и их необходимо обработать и исполнить. По-началу я интегрировал 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 за гораздо более полезные вещи: пробросы портов, трансфер файлов. Для данной статьи материала по ним не хватило. Примеры же можно посмотреть здесь.