Как стать автором
Обновить

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

Уровень сложностиПростой
Время на прочтение3 мин
Количество просмотров3.1K

Создать 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 за гораздо более полезные вещи: пробросы портов, трансфер файлов. Для данной статьи материала по ним не хватило. Примеры же можно посмотреть здесь.

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 10: ↑9 и ↓1+9
Комментарии1

Публикации