Docker – How to fix “Pseudo-terminal will not be allocated because stdin is not a terminal” in Alpine Linux

alpinedockershellterminal

I am writing a PHP program that runs a variety of shell commands. Sometimes it needs to call su, and by design, I want that to prompt for an elevated privilege password. Using passthru() in PHP works fine for this.

I have chosen to write functional tests only for my program, since it is dependent on ssh, su and other shell commands. I thus want to run the real thing inside PHPUnit to see if it is working as I expect.

Since this requires a SSH server and user account configurations, I have set up the tests to run in Docker. This approach looks like it will be fine – the image builds, when it runs it calls PHPUnit, and then exits. I expect there will be a way I can return a result via Docker to a calling system, such as Travis CI.

I have selected Alpine Linux as my base Docker image, but I am having problems with terminal allocation. I originally thought PHPUnit was getting in the way (see the original version of this question) but I have now narrowed it down to SSH, either run by PHP or even just at the console.

This works fine:

su -c whoami

However, this does not:

ssh localhost -t 'su -c whoami'

I get:

su: must be suid to work properly
Connection to localhost closed.

Note that ssh is set up with passwordless access to localhost (for testing purposes) so the only password I am expecting to enter is the su one.

The manual for ssh suggests that -f is helpful for where passwords to be asked for (the switch "requests ssh to go to background just before command execution") so I try this:

ssh localhost -t -f 'su -c whoami'

And I get:

Pseudo-terminal will not be allocated because stdin is not a terminal.
4275760bde94:~$ su: must be suid to work properly

Ah, another error! OK, so I've tried these ideas, particularly forcing a terminal:

ssh localhost -tt -f 'su -c whoami'

The double-t still emits complaints about SUID, and still does not work.

However, if I do this on my Ubuntu dev machine (also configured with PPK access to self) it works fine:

$ ssh localhost -t 'su -c whoami'
Password: 
root
Connection to localhost closed.

That makes it look like Alpine or BusyBox's OpenSSH is at fault. How can I dig into this further?

One solution is just to swap to another base distro, and Ubuntu would surely work fine, but that will massively inflate my Docker image size (currently on a very nice 68M total). So, I'd like to persist with Alpine for a bit if I can.

Unlikely to be Docker

I did wonder whether Docker might be preventing a terminal from being created or attached, but quickly discounted this, since I can get an interactive shell with docker exec -it container_name sh. Furthermore, I can do the ssh and then su in two separate commands inside a Docker shell just fine.

Bash doesn't help

I notice that Bash is available in Alpine, but this has not helped either, which surprises me:

/ $ apk add bash
bash-4.3$ bash
bash-4.3$ su nonpriv
bash-4.3$ ssh localhost whoami
nonpriv
bash-4.3$ ssh localhost 'su -c whoami'
su: must be suid to work properly
bash-4.3$ ssh localhost 'su -s /bin/bash -c whoami'
su: must be suid to work properly
bash-4.3$ ssh -t localhost 'su -s /bin/bash -c whoami'
su: must be suid to work properly
Connection to localhost closed.
bash-4.3$ ssh -tt localhost 'su -s /bin/bash -c whoami'
su: must be suid to work properly
Connection to localhost closed.
bash-4.3$ ssh -tf localhost 'su -s /bin/bash -c whoami'
Pseudo-terminal will not be allocated because stdin is not a terminal.
bash-4.3$ su: must be suid to work properly

bash-4.3$ ssh -ttf localhost 'su -s /bin/bash -c whoami'
bash-4.3$ su: must be suid to work properly
Connection to localhost closed.

Trying shell interpolation

I have found this nearly works:

/ $ ssh -t localhost "$( su -c whoami )"
sh: Password:: not found
sh: root: not found
Connection to localhost closed.

This uses the standard Alpine sh shell, and uses the "$()" construct, found on the aforementioned link, and which I do not fully understand. It outputs Password:, which is the prompt in su, and then waits for the password. When the password is entered the whoami is run, which prints root.

So it looks like it is running the command, but I am not sure if it is running the whoami immediately and then passing it to ssh (not what I want) or whether it is getting a remote shell to localhost then then doing it (this is what I intend).

In any case, it is trying to run stdout lines as if they were commands themselves. How can I get it just to print the output run via SSH?

Best Answer

I guessed in my question that this would work in an Ubuntu container, and that proved to be correct. Interestingly I still get the "must be run from a terminal" error with this command:

su -c whoami

But not here, where a terminal is allocated explicitly in an SSH passwordless command to self:

ssh -t localhost 'su -c whoami'

Unfortunately the new OS has bumped up my 68M image to 430M, urgh! I should therefore be most happy to receive new answers that get this working on Alpine.