-
Notifications
You must be signed in to change notification settings - Fork 203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add restore handler for password input #1312
Conversation
I'm not sure why the DCO fails. The commit and PR message have both the required |
The DCO is failing because you did not set your actual name for the git commit ( |
Ahhhhhhh, thanks. Fixed and force-pushed. |
110399a
to
cad3633
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vrothberg @mtrmac PTAL
pkg/auth/auth.go
Outdated
if oldState != nil { | ||
terminal.Restore(stdin, oldState) | ||
} | ||
os.Exit(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to call exit so far down in the stack. At least in podman we have handlers that depend on proper function, see libpod/shutdown
.
Also I have no idea where this code is used besides podman, I think it might be better to return an error so that the caller can decide what to do with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to handle this - In the podman repository I couldn't find any signal handlers for os.Interrupt
except for machine.go, which also uses os.Exit(1)
.
I can certainly modify it such that getUserAndPass
returns a fmt.Errorf("interrupted")
but I'm not sure if this is the desired behaviour. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I concur that we should return an error here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for contributing!
It's probably worth moving the logic around terminal.ReadPassword
into a separate function and update all callers (incl. pkg/ssh).
pkg/auth/auth.go
Outdated
if oldState != nil { | ||
terminal.Restore(stdin, oldState) | ||
} | ||
os.Exit(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I concur that we should return an error here.
I've updated the PR where I refactored a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICS this is just impossible to do with term.ReadPassword
: the original sample only works because of os.Exit
, which we don’t want to do.
Looking around a bit, I just can’t see any plausible solution (x/term
does have some code that attempts to handle Ctrl-C, but 1) in ICANON mode, it would only work after pressing Enter, and 2) we would have to signal.Ignore(os.Interrupt
), which is a disruptive operation that can’t be undone).
In the extreme, we could fork / spawn a subprocess. Ugh… I’m not even sure that is safe to do, and it would be so very ugly.
That wouldn’t, alone, be sufficient either, because Ctrl-C causes I wouldn’t entirely rule out the possibility of making all of that work, but the complexity (and dependency one of the most obscure aspects of POSIX — with Windows and other systems a whole another can of worms) makes it very attractive to me to suggest to just give up on this. Convincing users to use a more modern / featured shell that resets the terminal state before printing a prompt, or providing such patches to major shells, is starting to look easier than Podman handling this… One, sort of, alternative is to try to make the But, again, ugh. |
Thanks for the review and the input! I see two paths on how this could be addresses, both have their own caveats:
Please see suggestion 1 in the PR. I'm catching SIGINT, restore the terminal and then re-emit the signal. This is IMHO the least invasive change, as it should not alter the existing behaviour much: It would only restore the terminal as intermittent step.
This is ugly, but has the advantage, that Please see the current commit, where I added a suggestion for both methods. How should we continue? I have a preference for solution 1, but would leave it to someone with far better insight to suggest on how to proceed. Thank you! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think we can’t reasonably solve this locally in c/common.
90% of the problem is that golang.org/x/term.ReadPassword
needs to be interruptible (maybe using a context.Context
). That would make its existing implementation automatically take care of restoring the TTY configuration.
Then we would need to “only” care about turning a signal into a cancellation + error, and possibly turning the error back into a signal. That’s still not trivial — but having the cancellation feature would allow us to turn the signal into a cancellation+error using a very non-invasive signal.Notify
which doesn’t interfere with anything else in the process, and to turn the error into a signal at the very top of the Podman call stack, where there is nothing else to interfere with.
So, would anyone care to propose this to …/x/term
maintainers?
pkg/util/util.go
Outdated
term.Restore(fd, oldState) | ||
} | ||
signal.Stop(interruptChannel) | ||
syscall.Kill(syscall.Getpid(), syscall.SIGINT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect the goroutine to exit here instead of waiting on interruptChannel
(and hoping that the SIGINT
actually terminates the parent function, which is not guaranteed in presence of other code).
pkg/util/util.go
Outdated
term.Restore(fd, oldState) | ||
} | ||
signal.Stop(interruptChannel) | ||
syscall.Kill(syscall.Getpid(), syscall.SIGINT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion 1:
This works in a top-level application; it’s fairly suspect in library code. E.g. if there is anything else registered via signal.Notify
, it will now see the signal twice.
Also, even with this approach, I think we need to interrupt term.ReadPassword
, because there could be some other party with a signal handler; in that case even this syscall.Kill
might never terminate the term.ReadPassword
.
The good thing about this approach is that the exit code of the process actually reflects the signal. (BTW that doesn’t actually happen directly here, there is still a Go signal handler installed, but it does ~the same thing, uninstalling the handler and re-sending the signal.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My main concern is that there is no way of determining if there are other signal handlers for SIGINT present that attempt to gracefully terminate the application. This signal handler re-emits SIGINT and kills them. I could not find anything in os/signal
that allows us to check, if there are other handlers present.
This makes this solution undesirable, as it would kill possible other signal handlers, apart from your points.
pkg/util/util.go
Outdated
buf.buf, buf.err = term.ReadPassword(fd) | ||
inputChannel <- buf |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion 2:
With this, term.ReadPassword
continues running, consuming resources (meh), but potentially interfering: it continues to consume data from stdin, and it can eventually return and change the terminal state at any future time.
I can’t see how we can terminate the current implementation: fd
must be a terminal (so that ioctls work); and I can’t see that a pending read(3)
would be interrupted by a close(3)
.
A downside of turning the signal into an error is that the exit status of the process will not be traditionally correct. (In principle, we could turn it into a recognizable error, and turn that error into a signal at the very top of Podman.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. The only way of solving this would be to change term.ReadPassword
to handle interrupts itself gracefully.
My mistake: |
The issue is filed in golang/go#31180 and so far there has not been much activity or any visible attempts to solve this. If we would get an interruptible Looks like we're hitting a wall here. |
My suggestion for what to try would be to file a PR directly at https://github.com/golang/term , to add something like |
Thanks. I'll have a look and see what we can do. I leave this PR open and update it accordingly, so we keep track of the progress. |
I'm working on a prototype for Also: This requires a Unix/Windows implementation on a rather low level, so this is a nontrivial task. |
From a very quick look it seems that it could be possible to wrap the |
The problem with this approach is that a ongoing |
golang/term#12 as a first attempt to implement |
No reply for two weeks on golang/term#12. I also believe this is not going to fly because there two main issues that can not be resolved elegantly within the library function:
I believe we're hitting a dead end there as well. Has anyone an alternative idea how we would approach this issue? What do you think? |
I think that leaves the |
5788140
to
0cfe322
Compare
Reviving this PR. Please review my current suggestion. After multiple attempts we concluded that there is no perfect solution, as My suggestion based on #1312 (comment) is to implement a special Other attempts to do busy waiting on a non-blocking terminal might work as well but have their own caveats (increased system load and latency due to busy polling). Also they don't work on Windows. Asking @vrothberg @Luap99 on their opinion on this topic or if they have a better idea how to approach this issue. |
pkg/util/util_supported.go
Outdated
terminal.Restore(fd, oldState) | ||
} | ||
signal.Reset(os.Interrupt) | ||
os.Exit(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just return an error and give Podman a chance to properly exit? There is usually some cleanup do be done before Podman exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there is no way to return from terminal.ReadPassword
, so this would mean the terminal remains blocked.
We could return from the function, but that still leaves the terminal blocked until the return key is pressed, as we discussed in #1312 (comment). This is indeed the main issue where we have been hitting a wall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if you returned the error, users would need to hit or if you exit here users have to hit correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if I got your question right - If we return an error, the stdin would still be blocked by terminal.ReadPassword
until the user hits return.
We even tried to close the fd but not even that unblocks the read
call from terminal.ReadPassword
. So returning an error here on interrupt is possible, but it would mean that the terminal (stdin) remains in a weird state, which is likely not usable on subsequent calls. 🙁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can easily rewrite it such that the function returns an error, but then stdin remains blocked. This would allow podman to do all the cleanup routines with the caveat that stdin cannot be used in the error case.
I decided to go the path of terminating within the function, because that's how podman
currently handles the situation. From the functional standpoint this is the least intrusive change - the only difference is that the terminal is restored before podman
is being terminated anyways - either via os.Exit(1)
as in this PR or via SIGINT as it is of now.
How should we proceed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
man podman
...
125 The error is with podman itself
$ podman run --foo busybox; echo $?
Error: unknown flag: --foo
125
I guess it could be argued that killing Podman at this state is an error in Podman, so 125 is the correct error.
Other error codes usually come from the container or the lack of ability to execute a command within the container.
@mheon WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SIGINT handler in question should be https://github.com/containers/podman/blob/main/libpod/shutdown/handler.go but that doesn't help us much - that's nestled deep into Libpod and as such we can't touch it from here in c/common.
Generally speaking we should avoid os.Exit()
whenever possible (for the exact reasons we wrote the shutdown handler in Libpod - there are situations where we need to keep Podman alive long enough to finish essential cleanup before obeying user-initiated shutdown), so I'm in favor of returning an error, but I have no easy way of disabling the interrupt handler from within c/common to give us consistent error codes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that almost everyone in this threads favours an approach of returning an error. This doesn't remove the race condition with libpod/shutdown/handler.go, but it works in most of the cases I tested (1/19), so it would improve the situation already substantially.
I will update the PR accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rhatdan @vrothberg @mheon Please review. The discussed race condition with libpod/shutdown/handler.go remains, but there is little what we can do within this function as signal handlers cannot be disabled an re-enabled. See also #1312 (comment)
On my laptop, only in 1/19 cases the race condition prevented the terminal from being restored, in the other 18 cases it works as expected. It's not ideal, but given a missing viable alternative I'd argue that it still is an improvement and the ReadPassword
function behaves as desired.
Thanks for the patience!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think the answer here is to take a step back, I understand what this PR is trying to do and that looks reasonable.
If we look at the code path exactly podman login
bypasses pretty much all of libpod and calls directly in to pkg/auth here. So shouldn't be the answer disable the libpod setup for podman login/logout in which case there cannot be a race between the signal handlers.
b2c848f
to
6dd9c07
Compare
/approve |
91a353f
to
b8fab13
Compare
This commit restores the terminal state in case the program is interrupted while being in password read mode. This ensures the terminal remains usable, also if the password input is being cancelled. Signed-off-by: phoenix <[email protected]>
Rebased and applied |
@rhatdan ping? |
LGTM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: grisu48, rhatdan, vrothberg The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
This commit restores the terminal state in case the program is interrupted while being in password read mode. This ensures the terminal remains usable, also if the password input is being cancelled
This PR provides a solution for containers/podman#17372
Signed-off-by: Felix Niederwanger [email protected]