
SwiftNIO SSH é uma implementação programática do SSH usando SwiftNIO
Este projeto contém suporte SSH usando SwiftNIO.
SwiftNIO SSH é uma implementação programática do SSH: ou seja, é uma coleção de APIs que permitem que programadores implementem endpoints que falam SSH. Criticamente, isso significa que é mais parecido com o libssh2 do que com o openssh. O SwiftNIO SSH não entrega clientes e servidores SSH prontos para produção, mas sim fornece os blocos de construção para criar esse tipo de cliente e servidor.
Existem várias razões para fornecer uma implementação programática de SSH. Uma delas é que o SSH tem uma relação única com a interatividade do usuário. Usuários técnicos estão muito acostumados a interagir com o SSH de forma interativa, seja para executar comandos em máquinas remotas ou para executar shells interativos. Ter a capacidade de responder programaticamente a essas solicitações possibilita modos alternativos interessantes de interação. Como exemplos anteriores, podemos citar o Twisted's Manhole, que usa uma implementação programática de SSH chamada conch para fornecer um interpretador Python interativo dentro de um servidor Python em execução, ou ssh-chat, um servidor SSH que fornece uma sala de bate-papo em vez da funcionalidade normal de shell SSH. Usos inovadores também podem ser imaginados para o encaminhamento TCP.
Outra boa razão para fornecer SSH programático é que não é incomum que serviços precisem interagir com outros serviços de uma forma que envolva a execução de comandos. Embora Process resolva isso para o caso de uso local, às vezes os comandos que precisam ser invocados são remotos. Embora Process pudesse iniciar um cliente ssh como subprocesso para executar essa invocação, pode ser substancialmente mais simples invocar o SSH diretamente. Este é o caso de uso alvo do libssh2. O SwiftNIO SSH fornece o equivalente da camada de rede e criptográfica do libssh2, permitindo que usuários motivados conduzam sessões SSH diretamente de dentro de serviços Swift.
As versões mais recentes do SwiftNIO SSH suportam Swift 5.9 e superiores. A versão mínima do Swift suportada pelas versões do SwiftNIO SSH está detalhada abaixo:
SwiftNIO SSH suporta SSHv2 com o seguinte conjunto de recursos:
SwiftNIO SSH fornece um ChannelHandler do SwiftNIO, o NIOSSHHandler. Este handler implementa a maior parte do protocolo SSH diretamente. Não se espera que os usuários gerem mensagens SSH diretamente: em vez disso, eles interagem com o NIOSSHHandler através de canais filhos e delegados.
O SSH é um protocolo multiplexado: cada conexão SSH é subdividida em múltiplos canais de comunicação bidirecionais chamados, apropriadamente, de canais. O SwiftNIO SSH reflete essa construção usando uma abstração de "canal filho". Quando um par cria um novo canal SSH, o SwiftNIO SSH criará um novo Channel do NIO que é usado para representar todo o tráfego nesse canal SSH. Dentro deste Channel filho, todos os eventos são estritamente ordenados entre si: no entanto, eventos em diferentes Channels podem ser intercalados livremente pela implementação.
Uma conexão SSH ativa, portanto, se parece com isto:
┌ ─ NIO Channel ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ ┌────────────────────────────────┐ │
│ │
│ │ │ │
│ │
│ │ │ │
│ NIOSSHHandler │───────────────────────┐
│ │ │ │ │
│ │ │
│ │ │ │ │
│ │ │
│ └────────────────────────────────┘ │ │
│
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
│
│
│
│
▼
┌── SSH Child Channel ─────────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────┐ ┌────────────────────────────────┐ ├───┐
│ │ │ │ │ │ │
│ │ │ │ │ │ ├───┐
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ User Handler │ │ User Handler │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ └────────────────────────────────┘ └────────────────────────────────┘ │ │ │
│ │ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
Um canal SSH é invocado com um tipo de canal. O NIOSSH suporta três: session, directTCPIP e forwardedTCPIP. O tipo de canal mais comum é session: session é usado para representar a invocação de um programa, seja um programa específico nomeado ou um shell. Os outros dois tipos de canal estão relacionados ao encaminhamento de porta TCP e serão discutidos posteriormente.
Um canal SSH opera em um único tipo de dado: SSHChannelData. Esta estrutura encapsula o fato de que o SSH suporta dados de canal regulares e "estendidos". Os dados de canal regulares (SSHChannelData.DataType.channel) são usados para a grande maioria dos dados principais. Em canais session, o tipo de dado .channel é usado para entrada padrão e saída padrão: o tipo de dado .stdErr é usado para erro padrão (naturalmente). Em canais de encaminhamento TCP, o tipo de dado .channel é o único usado e representa os dados encaminhados.
Um canal session representa a invocação de um comando. Exatamente como o canal opera é comunicado através de vários eventos de usuário de entrada. Os seguintes eventos são importantes:
SSHChannelRequestEvent.PseudoTerminalRequest: Solicita a alocação de um pseudo-terminal.SSHChannelRequestEvent.EnvironmentRequest: Solicita uma única variável de ambiente para a invocação do comando. Sempre enviada antes do próprio comando.SSHChannelRequestEvent.ShellRequest: Solicita que o comando a ser invocado seja o shell do usuário autenticado.SSHChannelRequestEvent.ExecRequest: Solicita a invocação de um comando específico.SSHChannelRequestEvent.ExitStatus: Usado para sinalizar que o comando remoto terminou e comunica o código de saída.SSHChannelRequestEvent.ExitSignal: Usado para indicar que o comando remoto foi encerrado em resposta a um sinal e qual foi esse sinal.SSHChannelRequestEvent.SignalRequest: Usado para enviar um sinal ao comando remoto.SSHChannelRequestEvent.LocalFlowControlRequest: Usado para indicar se o cliente é capaz de realizar o controle de fluxo Ctrl-Q/Ctrl-S por si próprio.SSHChannelRequestEvent.WindowChangeRequest: Usado para comunicar uma alteração no tamanho da janela do terminal no cliente ao pseudo-terminal alocado.Esses eventos não são usados em mensagens de encaminhamento de porta. Implementações SSH que suportam canais do tipo .session precisam estar preparadas para lidar com a maioria ou todos esses eventos de várias maneiras.
Cada um desses eventos também tem um campo wantReply. Isso indica se a solicitação precisa de uma resposta para indicar sucesso ou falha. Se sim, os dois eventos a seguir são usados:
ChannelSuccessEvent, para comunicar sucesso.ChannelFailureEvent, para comunicar falha.O protocolo de rede SSH usa extensivamente o meio fechamento nos canais filhos. Os Channels do NIO normalmente têm suporte a meio fechamento desabilitado por padrão, e o SwiftNIO SSH respeita esse padrão em seus canais filhos também. No entanto, se você deixar essa configuração em seu valor padrão, os canais filhos SSH se comportarão de forma extremamente inesperada. Por esta razão, é fortemente recomendado que todos os canais filhos tenham o suporte a meio fechamento habilitado:
channel.setOption(ChannelOptions.allowRemoteHalfClosure, true)
Isso então usa o suporte padrão de meio fechamento do NIO. O par remoto enviando EOF será comunicado com um evento de usuário de entrada, ChannelEvent.inputClosed. Para enviar EOF você mesmo, chame close(mode: .output).
A autenticação de usuário é uma parte vital do SSH. Para gerenciá-la, o SwiftNIO SSH usa um par de protocolos delegados: NIOSSHClientUserAuthenticationDelegate e NIOSSHServerUserAuthenticationDelegate. Clientes e servidores devem fornecer implementações desses protocolos delegados para gerenciar a autenticação de usuário.
O protocolo do cliente é direto: o SwiftNIO SSH invocará o método nextAuthenticationType(availableMethods:nextChallengePromise:) no delegado. O availableMethods será uma instância de NIOSSHAvailableUserAuthenticationMethods comunicando quais métodos de autenticação o servidor sugeriu como aceitáveis. O delegado pode então completar o nextChallengePromise com uma nova solicitação de autenticação, ou com nil para indicar que o cliente ficou sem opções para tentar.
O protocolo do servidor é mais complexo. O delegado deve fornecer uma propriedade supportedAuthenticationMethods que comunica quais métodos de autenticação são suportados pelo delegado. Então, cada vez que o cliente envia uma solicitação de autenticação de usuário, o método requestReceived(request:responsePromise:) será invocado. Isso pode ser invocado várias vezes em paralelo, pois os clientes têm permissão para emitir solicitações de autenticação em paralelo. O responsePromise deve ser resolvido com o resultado da autenticação. Existem três resultados: .success e .failure são diretos, mas em princípio o servidor pode exigir múltiplos desafios usando .partialSuccess(remainingMethods:).
O encaminhamento de porta direto é o encaminhamento de porta do cliente para o servidor. Neste modo, tradicionalmente o cliente escutará em uma porta local e encaminhará as conexões de entrada para o servidor. Ele pedirá que o servidor encaminhe essas conexões como conexões de saída para um host e porta específicos.
Esses canais podem ser abertos diretamente pelos clientes usando o tipo de canal .directTCPIP.
O encaminhamento de porta remoto é uma situação menos comum onde o cliente pede ao servidor para escutar em um endereço e porta específicos e encaminhar todas as conexões de entrada para o cliente. Como o cliente precisa solicitar esse comportamento, ele o faz usando solicitações globais.
As solicitações globais são iniciadas usando NIOSSHHandler.sendGlobalRequest e são recebidas e tratadas por meio de um GlobalRequestDelegate. Atualmente, duas solicitações globais são suportadas:
GlobalRequest.TCPForwardingRequest.listen(host:port:): uma solicitação para o servidor escutar em um determinado host e porta.GlobalRequest.TCPForwardingRequest.cancel(host:port:): uma solicitação para cancelar a escuta no host e porta fornecidos.Os servidores podem ser notificados e responder a essas solicitações usando um GlobalRequestDelegate. O método a ser implementado aqui é tcpForwardingRequest(_:handler:promise:). Este método delegado será invocado sempre que uma solicitação global for recebida. A resposta à solicitação é passada para promise.
Os canais encaminhados são então enviados do servidor para o cliente usando o tipo de canal .forwardedTCPIP.
| SwiftNIO SSH | Versão Mínima do Swift |
|---|
0.0.0 ..< 0.3.0 | 5.1 |
0.3.0 ..< 0.4.0 | 5.2 |
0.4.0 ..< 0.5.0 | 5.4 |
0.5.0 ..< 0.6.2 | 5.5.2 |
0.6.2 ..< 0.9.0 | 5.6 |
0.9.0 ..< 0.9.2 | 5.8 |
0.9.2 ..< 0.10.0 | 5.9 |
0.10.0 ... 0.12.0 | 5.10 |
0.12.0 ..< 0.13.0 | 6.0 |
0.13.0 ..< | 6.1 |
SSHChannelRequestEvent.SubsystemRequest: Usado para solicitar a invocação de um subsistema específico. O significado disso é específico para cada caso de uso.