Coerce scp (1), sftp (1) ou rsync (1) para transferir um pipe nomeado

5

Eu pretendo transferir backups completos e incrementais dos meus subvolumes btrfs para um serviço de arquivamento em fita. O serviço expõe terminais FTP e SSH. Se eu tivesse permissão para executar comandos arbitrários no ponto de extremidade SSH, eu faria o seguinte para executar um backup incremental:

btrfs send -p $LAST_SUBVOLUME $NEXT_SUBVOLUME | compress | encrypt |
    ssh -p $PORT $USER@$ENDPOINT "cat > $SUBVOLUME.$YYYYMMDD.btrfs.bz2.gpg"

Não posso, no entanto, fazer isso:

$ ssh -p $PORT $USER@$ENDPOINT
Last login: Mon Jan  3 01:23:45 2067 from 123.456.789.123

This account is restricted by rssh.
Allowed commands: scp sftp rsync 

If you believe this is in error, please contact your system administrator.

Connection to some.remote.endpoint closed.

Então, o que eu pensei em fazer foi usar o protocolo SCP para a transferência. No entanto, meu binário scp se recusa a transferir um pipe nomeado:

$ scp -P $PORT <(btrfs send -p $LAST_SUBVOLUME $NEXT_SUBVOLUME | compress | encrypt) \
    $USER@$ENDPOINT:$SUBVOLUME.$YYYYMMDD.btrfs.bz2.gpg
/dev/fd/63: not a regular file

A ironia é que aparentemente, scp costumava fazer a coisa certa uma vez . Suponho que nem todos pensavam que a transferência de pipes nomeados poderia ser sensata / útil. EDIT (2017-06-03): Esta foi uma observação incorreta. Como observado por Kenster, o protocolo SCP não permite enviar arquivos de tamanho desconhecido.

UPDATE (2017-06-04): Eu também tentei transferir os dados usando o protocolo SFTP. Aparentemente, o protocolo FTP permite enviar arquivos de tamanho desconhecido, como evidenciado pelo suporte para dados de tubulação no ftp ( link ) e ncftpput ( link , seção Descrição, último parágrafo) binários. Não encontrei tal suporte nos clientes SFTP que tentei ( sftp , lftp ), o que pode ser uma indicação de que o SFTP (ao contrário do FTP) não suporta o envio de arquivos de tamanho desconhecido (ao contrário do que eu pensava, SFTP é não apenas FTP tunelado sobre SSH; é um protocolo diferente).

UPDATE (2017-06-05): De acordo com a versão preliminar do protocolo SFTP versão 3 ( link ), cada pacote no nível do aplicativo deve especificar o tamanho da carga antes da carga útil ( link , seção 3). No entanto, o SFTP suporta buscas no arquivo escrito ( link , Seção 6.4) com suporte explícito para gravações além do final atual do arquivo. Portanto, se for possível usar um pequeno buffer no lado do cliente e enviar um arquivo de tamanho desconhecido em pequenos pedaços de tamanho conhecido:

#!/bin/bash
# <Exchange SSH_FXP_INIT requests.>
# <Send an SSH_FXP_OPEN request.>
CHUNK_SIZE=32768
OFFSET=0
IFS=''; while read -r -N $CHUNK_SIZE CHUNK; do
  ACTUAL_SIZE='cat <<<"$CHUNK" | head -c -1 | wc -c'
  # <Send an SSH_FXP_WRITE request with payload $CHUNK of size
  # $ACTUAL_SIZE at offset $OFFSET.>
  OFFSET=$(($OFFSET+$ACTUAL_SIZE))
done < <(command)
# <Send an SSH_FXP_CLOSE request.>

No entanto, fazer a comunicação manualmente através do shell seria bastante doloroso. Eu estou procurando por um cliente SFTP que expõe esse tipo de funcionalidade.

Referências

por Witiko 03.06.2017 / 11:45

3 respostas

3

lftp 4.6.1 e mais recentes devem ser capazes de fazer isso: link

Infelizmente, o comando sugerido no problema vinculado não funciona, mas um comando ligeiramente modificado:

lftp -p $port sftp://$user:$pass@$host -e "put /dev/stdin -o $filename"

Devido a um bug , você deve fornecer algo no campo de senha se usar um agente SSH. Mas qualquer string aleatória serve.

Este comando lê a partir de /dev/stdin . Se você precisar usar um pipe nomeado diferente, imagino que isso também funcione - se não, você sempre pode cat named-pipe | lftp ... .

Testei lftp com um upload de > 100 GB sem problemas.

rclone também suporta SFTP, e deve ser capaz de fazer upload de uma entrada canalizada com o comando rcat , que será lançado em 1.38 (próxima versão).

    
por 22.09.2017 / 10:05
6

O SCP não é adequado para o seu objetivo. O protocolo SCP não suporta o envio de um fluxo de bytes de tamanho desconhecido para o sistema remoto a ser salvo como um arquivo. A mensagem do protocolo SCP para enviar um arquivo requer que o tamanho do arquivo seja enviado primeiro, seguido pelos bytes que compõem o arquivo. Com um fluxo de bytes lidos a partir de um pipe, você normalmente não saberia quantos bytes o pipe produzirá, portanto não há como enviar uma mensagem do protocolo SCP incluindo o tamanho correto.

(As únicas descrições online do protocolo SCP que encontrei são aqui e aqui . na descrição da mensagem "C".)

O protocolo SFTP pode ser usado para esse tipo de coisa. Tanto quanto eu sei, o utilitário de linha de comando normal sftp não suporta a leitura de um pipe e o armazena como um arquivo remoto. Mas existem bibliotecas SSH / SFTP para a maioria das linguagens de programação modernas (perl, python, ruby, C #, Java, C, etc). Se você souber como usar um desses idiomas, deve ser fácil escrever um utilitário que faça o que você precisa.

Se você está preso a scripts de shell, é possível falsificar o suficiente do protocolo SCP para transferir um arquivo. Aqui está um exemplo:

#!/bin/bash
cmd='cat /etc/group'

size=$($cmd | wc -c)    
{
        echo C0644 $size some-file
        $cmd
        echo -n -e '
#!/bin/bash
cmd='cat /etc/group'

size=$($cmd | wc -c)    
{
        echo C0644 $size some-file
        $cmd
        echo -n -e '%pre%0'
} | ssh user@host scp -v -p -t /some/directory
0' } | ssh user@host scp -v -p -t /some/directory

Isso criará some-file em /some/directory no sistema remoto com permissões 644. O conteúdo do arquivo será o que for que $cmd grava em sua saída padrão. Observe que você está executando o comando duas vezes, com qualquer consumo de recursos e efeitos colaterais que isso implique. E o comando deve produzir o mesmo número de bytes de cada vez.

    
por 03.06.2017 / 18:48
2

Se você pode escrever perl, você pode tentar usar Net :: SFTP :: Foreign que é capaz de transferir arquivos de um descritor de arquivo aberto via SFTP:

#!/usr/bin/perl
# untested!
use Net::SFTP::Foreign;
my $user = ...;
my $host = ...;
my $remote_path = ...;
my $btrfs_cmd = 'btrfs send -p $LAST_SUBVOLUME $NEXT_SUBVOLUME';
my $sftp = Net::SFTP::Foreign->new("$user\@$host", ...);
open my($pipe), "$btrfs_cmd | compress | encrypt |"
    or die $!;
$sftp->put($fh, $remote_path);
    
por 24.10.2017 / 21:58

Tags