Como posso executar comandos arbitrariamente complexos usando sudo sobre ssh?

80

Eu tenho um sistema no qual só posso fazer login com meu nome de usuário (myuser), mas preciso executar comandos como outro usuário (scriptuser). Até agora, vim com o seguinte para executar os comandos necessários:

ssh -tq myuser@hostname "sudo -u scriptuser bash -c \"ls -al\""

Se, no entanto, quando tento executar um comando mais complexo, [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"rapidamente encontro problemas com a citação. Não tenho certeza de como poderia passar esse exemplo de comando complexo para bash -c, quando \"já delimita os limites do comando que estou passando (e, portanto, não sei como citar o diretório / tmp / Some, que inclui espaços.

Existe uma solução geral que me permita passar qualquer comando, não importa quão complexa / louca seja a citação, ou é algum tipo de limitação que eu alcancei? Existem outras soluções possíveis e talvez mais legíveis?

VoY
fonte
11
Copie um script para $ remote e execute-o.
user9517
Isso funcionaria, mas acho que uma solução que não deixa arquivos de script para trás (caso algo falhe etc.) seria um pouco mais limpa.
VoY
9
ter o script rm próprio no final de cada execução
thanasisk
3
Considere usar fabric fabfile.org . Pode tornar sua vida muito mais fácil se você tiver que sudo muito remotamente.
Nils Toedtmann
2
Se você tem Ansible instalado este comando mostra a documentação para o seu caso de uso: roteiro ansible-doc
bbaassssiiee

Respostas:

146

Um truque que uso às vezes é usar o base64 para codificar os comandos e canalizá-lo para o bash no outro site:

MYCOMMAND=$(base64 -w0 script.sh)
ssh user@remotehost "echo $MYCOMMAND | base64 -d | sudo bash"

Isso codificará o script, com vírgulas, barras invertidas, aspas e variáveis ​​dentro de uma string segura, e o enviará para o outro servidor. ( -w0é necessário para desativar a quebra de linha, o que acontece na coluna 76 por padrão). Por outro lado, $(base64 -d)decodificará o script e alimentará o bash para ser executado.

Eu nunca tive nenhum problema com isso, não importa quão complexo o script fosse. Resolve o problema de escapar, porque você não precisa escapar de nada. Ele não cria um arquivo no host remoto e você pode executar scripts muito complicados com facilidade.

ThoriumBR
fonte
2
Ou até:ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash"
Niklas B.
1
Vale a pena notar que, na maioria dos casos, você pode substituir o lzop ou o gzip pelo base64 para obter tempos de transferência mais rápidos. No entanto, pode haver casos extremos. YMMV.
CodeGnome 4/14
1
Boa nota, mas se for um script, não espero que nenhuma transferência seja superior a alguns kb.
ThoriumBR
7
Também há um uso inútil de eco aqui. base64 script | ssh remotehost 'base64 -d | sudo bash'seria suficiente :)
hobbs 5/09
Enquanto eu não testei esta solução, ela produzirá o resultado do script, por exemplo, 'yum check-update' no stdout? Eu queria perguntar, porque se alguém pensa que estou fazendo algo obviamente ingênuo, posso pegar algumas coisas.
Soham Chakraborty
34

Veja a -ttopção? Leia o ssh(1)manual.

ssh -tt root@host << EOF
sudo some # sudo shouldn't ask for a password, otherwise, this fails. 
lines
of
code 
but be careful with \$variables
and \$(other) \`stuff\`
exit # <- Important. 
EOF

Uma coisa que costumo fazer é usar o vim e usar o :!cat % | ssh -tt somemachinetruque.

moebius_eye
fonte
3
Claro que :!cat % | (command)pode ser feito como :w !(command)ou :!(command) < %.
Scott Scott
2
Sim. Minha linha é um abuso de gato
moebius_eye
running ssh -ttfaz com que meu ssh para não terminar após certos comandos são executados (adicionando exitno final não ajuda)
10

Acho que a solução mais fácil está na modificação do comentário do @ thanasisk.

Crie um script scpna máquina e execute-o.

Tenha o rmpróprio script no início. O shell abriu o arquivo, então ele foi carregado e pode ser removido sem problemas.

Ao fazer as coisas nesta ordem ( rmprimeiro, outras coisas em segundo), ele será removido mesmo quando falhar em algum momento.

dr. Sybren
fonte
8

Você pode usar o %qespecificador de formato printfpara cuidar da variável que está escapando:

cmd="ls -al"
printf -v cmd_str '%q' "$cmd"
ssh user@host "bash -c $cmd_str"

printf -vgrava a saída em uma variável (neste caso, $cmd_str). Eu acho que essa é a maneira mais simples de fazer isso. Não há necessidade de transferir nenhum arquivo ou codificar a sequência de comandos (por mais que eu goste do truque).

Aqui está um exemplo mais complexo, mostrando que ele também funciona para colchetes e e comercial:

$ ssh user@host "ls -l test"
-rw-r--r-- 1 tom users 0 Sep  4 21:18 test
$ cmd="[[ -f test ]] && echo 'this really works'"
$ printf -v cmd_str '%q' "$cmd"
$ ssh user@host "bash -c $cmd_str"
this really works

Ainda não testei, sudomas deve ser tão simples quanto:

ssh user@host "sudo -u scriptuser bash -c $cmd_str"

Se desejar, você pode pular uma etapa e evitar a criação da variável intermediária:

$ ssh user@host "bash -c $(printf '%q' "$cmd")"
this really works

Ou até mesmo evite criar uma variável inteiramente:

ssh user@host "bash -c $(printf '%q' "[[ -f test ]] && echo 'this works as well'")"
Tom Fenech
fonte
1
Esta é uma solução incrível. Na verdade, tive que usar printf antes para uma situação semelhante, mas sempre esqueço disso. Obrigado por postar este :-)
Jon L.
8

Aqui está a maneira "correta" (sintática) de executar algo como isto no bash:

ssh user@server "$( cat <<'EOT'
echo "Variables like '${HOSTNAME}' and commands like $( uname -a )"
echo "will be interpolated on the server, thanks to the single quotes"
echo "around 'EOT' above.
EOT
)"

ssh user@server "$( cat <<EOT
echo "If you want '${HOSTNAME}' and $( uname -a ) to be interpolated"
echo "on the client instead, omit the the single quotes around EOT."
EOT
)"

Para uma explicação detalhada de como isso funciona, consulte https://stackoverflow.com/a/21761956/111948

Dejay Clayton
fonte
5

Você sabe que pode usar sudopara fornecer um shell no qual você pode executar comandos como o usuário escolhido?

-i, --login

Execute o shell especificado pela entrada do banco de dados de senha do usuário de destino como um shell de logon. Isso significa que os arquivos de recursos específicos de login, como .profile ou .login, serão lidos pelo shell. Se um comando for especificado, ele será passado para o shell para execução através da opção -c do shell. Se nenhum comando for especificado, um shell interativo será executado. O sudo tenta mudar para o diretório inicial desse usuário antes de executar o shell. O comando é executado com um ambiente semelhante ao que um usuário receberia ao efetuar login. A seção Ambiente de Comando no manual do sudoers (5) documenta como a opção -i afeta o ambiente em que um comando é executado quando a política de sudoers está em uso.

justinpc
fonte
essa parece ser a solução óbvia para mim. > sudo -i -u brian
code_monk
Isso funciona melhor quando combinado com "ssh -t" no caso de acesso remoto. O que está incluído na pergunta, mas vale a pena repetir para o pessoal "Não consigo ler esta / página inteira /". :)
dannysauer
4

Você pode definir o script na sua máquina local e, em seguida, catcanalizá-lo para a máquina remota:

user@host:~/temp> echo "echo 'Test'" > fileForSsh.txt
user@host:~/temp> cat fileForSsh.txt | ssh localhost

Pseudo-terminal will not be allocated because stdin is not a terminal.
stty: standard input: Invalid argument
Test
jas_raj
fonte
5
UUOC você pode usar <
user9517
Você pode modificar o exemplo para incluir sudo -u scriptuser? O heredoc seria utilizável, considerando que eu preciso ter variáveis ​​no script que eu estaria canalizando para a máquina?
VoY
Eu estava tentando: echo "sudo `cat fileForSsh.txt`" | ssh ...mas continuo recebendo sudo: sorry, you must have a tty to run sudo.
jas_raj
Embora esta pergunta pode ajudar se você pode obter o /etc/sudoersarquivo alterado
jas_raj
1
Isso funciona para mim:ssh -tq user@host "sudo bash -s" < test.sh
AVee
3

Simples e fácil.

ssh usuário @ servidor "bash -s" <script.sh

rafaelrms
fonte
1

Se você usar um shell Bash suficientemente moderno, poderá colocar seus comandos em uma função e imprimi-la como uma string usando export -p -f function_name. O resultado dessa cadeia de caracteres pode conter comandos arbitrários que não precisam necessariamente ser executados como raiz.

Um exemplo usando pipes da minha resposta no Unix.SE :

#!/bin/bash
remote_main() {
   local dest="$HOME/destination"

   tar xzv -C "$dest"
   chgrp -R www-data "$dest"
   # Ensure that newly written files have the 'www-data' group too
   find "$dest" -type d -exec chmod g+s {} \;
}
tar cz files/ | ssh user@host "$(declare -pf remote_main); remote_main"

Um exemplo que recupera salva o arquivo para o usuário de login e instala um programa como raiz:

remote_main() {
    wget https://example.com/screenrc -O ~/.screenrc
    sudo apt-get update && sudo apt-get install screen
}
ssh user@host "$(declare -pf remote_main); remote_main"

Se você deseja executar o comando inteiro usando sudo, você pode usar o seguinte:

remote_main() {
    wget https://example.com/screenrc -O ~user/.screenrc
    apt-get update && apt-get install screen
}
ssh user@host "$(declare -pf remote_main);
    sudo sh -c \"\$(declare -pf remote_main); remote_cmd\""
# Alternatively, if you don't need stdin and do not want to log the command:
ssh user@host "$(declare -pf remote_main);
    (declare -pf remote_main; echo remote_cmd) | sudo sh"
Lekensteyn
fonte
1

Aqui está a minha solução ruim (não realmente testada) para isso:

#!/usr/bin/env ruby
# shell-escape: Escape each argument.
ARGV.each do|a|
  print " '#{a.gsub("'","\'\\\\'\'")}' "
end

Você não pode fazer:

ssh -tq myuser@hostname "$(shell-escape sudo -u scriptuser bash -c "$(shell-escape ls -al)")"

O próximo passo é criar um bettersshscript que já faça isso:

betterssh -tq myuser@hostname sudo -u scriptuser bash -c "$(shell-escape ls -al)"
ysdx
fonte
1

Para aqueles que precisam passar parâmetros para o script, deve ser o seguinte:

ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash -s <param1> <param2> <paramN>"
Romande
fonte
1

Primeiro, crie um script local e execute-o remotamente usando o seguinte comando:

cat <Local Script.sh> | ssh user@server "cat - | sudo -u <user> /bin/bash"
Franciscon Santos
fonte