ssh over sudo를 사용하여 임의의 복잡한 명령을 어떻게 실행할 수 있습니까?


80

내 사용자 이름 (myuser)으로 만 로그인 할 수있는 시스템이 있지만 다른 사용자 (scriptuser)로 명령을 실행해야합니다. 지금까지 필요한 명령을 실행하기 위해 다음을 생각해 냈습니다.

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

그러나 더 복잡한 명령을 실행하려고하면 [[ -d "/tmp/Some directory" ]] && rm -rf "/tmp/Some directory"인용에 문제가 생길 수 있습니다. 나는이 예 복잡한 명령을 전달할 수있는 방법을 잘 모르겠어요 bash -c때, \"이미 명령을 내가 전달하고있어 (그래서 나는 공백이 포함 / tmp를 / 일부 디렉토리를 인용하는 방법을 모르는의 경계를 delimites.

인용이 아무리 복잡하거나 미친 지에 관계없이 명령을 전달할 수있는 일반적인 해결책이 있습니까? 아니면 이것이 어떤 한계에 도달 했습니까? 다른 가능하고 더 읽기 쉬운 솔루션이 있습니까?


11
스크립트를 $ remote에 복사 한 다음 실행하십시오.
user9517

그것은 효과가 있지만 스크립트 파일을 남기지 않는 해결책이 있습니다 (뭔가 실패하는 경우 등)는 약간 깨끗합니다.
VoY

9
각 실행이 끝날 때마다 스크립트 자체를 작성하십시오.
thanasisk

3
패브릭 fabfile.org 사용을 고려하십시오 . 원격으로 많이 sudo 해야하는 경우 훨씬 쉽게 인생을 만들 수 있습니다.
Nils Toedtmann

2
Ansible을 설치 한 경우이 명령은 사용 사례에 대한 설명서를 표시합니다. ansible-doc script
bbaassssiiee

답변:


146

때때로 사용하는 트릭은 base64를 사용하여 명령을 인코딩하고 다른 사이트에서 bash로 파이프하는 것입니다.

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

이렇게하면 안전한 문자열 안에 쉼표, 백 슬래시, 따옴표 및 변수가 포함 된 스크립트가 인코딩되어 다른 서버로 전송됩니다. ( -w0기본적으로 열 76에서 발생하는 줄 바꿈을 비활성화하는 데 필요합니다). 다른 한편으로, $(base64 -d)스크립트를 해독하고 실행할 bash에 공급합니다.

스크립트가 아무리 복잡해도 아무 문제가 없습니다. 탈출 할 필요가 없기 때문에 탈출 문제를 해결하십시오. 원격 호스트에 파일을 작성하지 않으며 매우 복잡한 스크립트를 쉽게 실행할 수 있습니다.


2
ssh user@remotehost "echo `base64 -w0 script.sh` | base64 -d | sudo bash"
Niklas B.

1
대부분의 경우 더 빠른 전송 시간을 위해 lzop 또는 gzip을 base64 대신 사용할 수 있습니다. 그러나 엣지 케이스가있을 수 있습니다. YMMV.
CodeGnome

1
참고 사항이지만 스크립트 인 경우 전송이 몇 kb 이상이 될 것으로 기대하지 않습니다.
ThoriumBR

7
여기에도 쓸모없는 에코가 있습니다. base64 script | ssh remotehost 'base64 -d | sudo bash'충분할 것입니다 :)

이 솔루션을 테스트하지는 않았지만 스크립트의 결과 (예 : stdout의 'yum check-update')가 출력됩니까? 누군가가 내가 순진한 일을하고 있다고 생각하면 몇 가지를 선택할 수 있기 때문에 묻고 싶었습니다.
Soham Chakraborty

34

참고 항목 -tt옵션을? ssh(1)설명서를 읽으십시오 .

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

내가 자주하는 일 중 하나는 vim을 사용하고 :!cat % | ssh -tt somemachine트릭을 사용하는 것 입니다.


3
물론 또는 :!cat % | (command)로 수행 할 수 있습니다 . :w !(command):!(command) < %
Scott

2
예. 내 라인은 고양이 학대입니다
moebius_eye

running ssh -tt특정 명령이 실행 된 후 내 ssh가 종료 exit되지 않습니다 (끝에 추가 해도 도움이되지 않음)

10

가장 쉬운 해결책은 @thanasisk의 의견을 수정하는 것입니다.

scp컴퓨터에 스크립트를 작성한 다음 실행하십시오.

rm처음에 스크립트 자체를 가지고 있어야합니다 . 셸에서 파일을 열었으므로 파일이로드 된 후 문제없이 제거 할 수 있습니다.

이 순서대로 작업을 수행하면 ( rm첫째, 다른 작업은 두 번째로) 어느 시점에서 실패하면 제거됩니다.


8

%qformat 지정자를 사용하여 printf변수 이스케이프를 처리 할 수 ​​있습니다.

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

printf -v출력을 변수에 씁니다 (이 경우 $cmd_str). 나는 이것이 가장 간단한 방법이라고 생각합니다. 파일을 전송하거나 명령 문자열을 인코딩 할 필요가 없습니다 (트릭을 좋아하는만큼).

다음은 대괄호 및 앰퍼샌드와 같은 작업에도 적용되는보다 복잡한 예입니다.

$ 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

나는 그것을 테스트하지 않았지만 다음과 sudo같이 간단해야합니다.

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

원하는 경우 단계를 건너 뛰고 중간 변수 작성을 피할 수 있습니다.

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

또는 변수를 완전히 작성하지 마십시오.

ssh user@host "bash -c $(printf '%q' "[[ -f test ]] && echo 'this works as well'")"

1
이것은 멋진 솔루션입니다. 비슷한 상황에서 실제로 printf를 사용해야했지만 항상 잊어 버립니다. 이것을 게시 해 주셔서 감사합니다 :-)
Jon L.

8

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
)"

작동 방식에 대한 자세한 설명은 https://stackoverflow.com/a/21761956/111948을 참조 하십시오.


5

sudo선택한 사용자로서 명령을 실행할 수있는 쉘을 제공 하는 데 사용할 수 있다는 것을 알고 있습니까?

-i,-로그인

대상 사용자의 비밀번호 데이터베이스 항목으로 지정된 쉘을 로그인 쉘로 실행하십시오. 이는 .profile 또는 .login과 같은 로그인 특정 자원 파일을 쉘에서 읽음을 의미합니다. 명령이 지정되면 쉘의 -c 옵션을 통해 실행되도록 쉘로 전달됩니다. 명령을 지정하지 않으면 대화식 쉘이 실행됩니다. sudo는 쉘을 실행하기 전에 해당 사용자의 홈 디렉토리로 변경하려고 시도합니다. 명령은 사용자가 로그인 할 때와 비슷한 환경에서 실행됩니다. sudoers (5) 매뉴얼의 명령 환경 섹션은 -i 옵션이 명령이 실행되는 환경에 어떤 영향을 미치는지 문서화합니다. sudoers 정책이 사용 중입니다.


이것은 나에게 명백한 해결책처럼 보인다. > sudo -i -u brian
code_monk

원격 액세스의 경우 "ssh -t"와 함께 사용할 때 가장 효과적입니다. 질문에 포함되어 있지만 "이 / whole / page를 읽을 수 없습니다"사람들에 대해 반복해서 설명합니다. :)
dannysauer

4

로컬 머신에서 스크립트를 정의한 후 cat원격 머신으로 파이프 할 수 있습니다.

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

5
UUOC 당신은 <사용할 수 있습니다
user9517

포함하도록 예제를 수정할 수 있습니까 sudo -u scriptuser? 기계에 배관 할 스크립트에 변수가 필요하다는 것을 고려할 때 heredoc을 사용할 수 있습니까?
VoY

나는 노력하고 있었다 : echo "sudo `cat fileForSsh.txt`" | ssh ...그러나 나는 계속 받고있다 sudo: sorry, you must have a tty to run sudo.
jas_raj

하지만 이 질문은 당신이 얻을 수있는 경우에 도움이 될 수 /etc/sudoers파일이 변경
jas_raj

1
이것은 저에게 효과적입니다 :ssh -tq user@host "sudo bash -s" < test.sh
AVee

3

간단하고 쉽습니다.

ssh user @ servidor "bash -s"<script.sh


1

충분히 현대적인 Bash 쉘을 사용하는 경우 명령을 함수에 넣고을 사용하여 해당 함수를 문자열로 인쇄 할 수 있습니다 export -p -f function_name. 그러면 해당 문자열의 결과에 루트로 실행할 필요가없는 임의의 명령이 포함될 수 있습니다.

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"

검색하는 예제는 로그인 사용자의 파일을 저장하고 프로그램을 루트로 설치합니다.

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"

를 사용하여 전체 명령을 실행하려면 sudo다음을 사용할 수 있습니다.

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"

1

이것에 대한 (실제 테스트되지 않은) crappy 솔루션은 다음과 같습니다.

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

당신은 할 수 없습니다 :

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

다음 단계는 betterssh이미이 작업을 수행 하는 스크립트 를 작성하는 것입니다.

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

1

스크립트에 매개 변수를 전달해야하는 사용자의 경우 다음과 같아야합니다.

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

1

먼저 로컬 스크립트를 만든 후 follow 명령을 사용하여 원격으로 실행하십시오.

cat <Local Script.sh> | ssh user@server "cat - | sudo -u <user> /bin/bash"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.