subprocess.Popen for “ssh sudo -u user -i” is fail

2020-07-22 09:47发布

问题:

I want to ssh into a remote server, change user then execute a script. I'm using subprocess to do this but it appears sudo -u userB -i is not changing user.

HOST = 'remote_server'
USER = 'userA'
CMD = ' whoami; sudo -u userB -i; whoami'

ssh = subprocess.Popen(['ssh', '{}@{}'.format(USER, HOST),CMD],
                            shell=False,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
result = ssh.stdout.readlines()   
if not result:
    err = ssh.stderr.readlines()
    print('ERROR: {}'.format(err))
else:
    print "success"
    print(result) 

$ success
$ ['userA\n', 'userA\n']

When I replaced with CMD ='sudo -u userB -i && whoami', I got this error:

ERROR: ['sudo: sorry, you must have a tty to run sudo\n']

On the terminal, I'm able to do passwordless ssh, sudo -u userB -i && whoami

回答1:

sudo on the target system is configured to require a terminal emulation. That is usually done to actually prevent the kind of automation you are trying to implement. Talk to your server administrator, before trying to bypass that restriction. Either the restriction is not needed, then the admin can turn it off for you. Or there is a reason for the restriction and the Administrator won't be happy that you are trying to bypass it.

The restriction can be turned off by removing requiretty option from sudoers file (the option is off by default).


To make ssh use the terminal emulation for the command provided on its command-line, you need to use -t switch. But for -t to actually have any effect, you need to run ssh from a terminal in the first place. As subprocess.Popen is not a terminal, you will get:

Pseudo-terminal will not be allocated because stdin is not a terminal

To bypass that, you have to use double -t switch: -tt.


To @SuryaSekharMondal (who offered bounty on this question): You are using Paramiko, not ssh. What I wrote above is not relevant to you.



回答2:

Inserting this line in /etc/sudoers on the remote server will resolve the issue:

Defaults    !requiretty


回答3:

I would approach this problem with a small C program and give it the suid permission, i.e.

#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
// #include <stdio.h>
#include <string.h>
int main(int argc,char *argv[]){
    setuid(0);//su
    char *cmd="/usr/sbin/hddtemp";
    char *arg[]={(char *)"hddtemp",(char *)"--numeric"};
    #define array_size(x) (sizeof(x)/sizeof(*x))
    int size=array_size(arg);
    size_t i,n=size+argc;
    char **args=malloc((n)*sizeof(char*));
    if(args==NULL)return 255;
    for(i=0;i<size;i++){
        args[i]=malloc(strlen(arg[i])+1);
        if(args[i]==NULL)return 255;
        strcpy(args[i],arg[i]);
    }
    for(i=0;i<argc-1;i++){
        args[size+i]=malloc(strlen(argv[i+1])+1);
        if(args[size+i]==NULL)return 255;
        strcpy(args[size+i],argv[i+1]);
    }
    // printf ("total args %2zu\n",i);
    // for(i=0;i<n;i++)printf ("args[%2zu] : %s\n",i,args[i]);
    execv(cmd,args);
    return 1;
}

First, compile with:

gcc -Wall program.c -o program

Then, chown and chmod to

chown 0:userB program
chmod 4750 program

And, if you don't have the need to mix hardcoded arguments with passed ones you could just use the following:

#include <unistd.h>
int main(int argc,char *argv[]){
    setuid(1000); //user id of userB
    char *cmd="/usr/bin/whoami";
    execv(cmd,argv);
    return 1; // indicate error if execv() didn't succeed.
}

In general, every admin will advise you against adding exceptions to /etc/suders and there's a good reason why.