Search code examples
haskellsudo

Haskell: Multiple long-running `callCommand "sudo ..."`


I have a Haskell script that runs long-running some shell commands with sudo:

do callCommand "sudo somethingLong"
   callCommand "sudo somethingElseLong"

Problem is, it asks for my password for each invocation of callCommand "sudo ...". For short-running command this isn't a problem, but if they are long enough it'll ask again.

Is there a way to stop this behaviour? So it just asks for my password once until the script is done?


Solution

  • The default sudo "security policy" is to cache credentials on a per-terminal basis for 15 minutes. As long as a sudo command is executed on the terminal at an interval that's less than 15 minutes, you should be able to maintain the cached credentials. Also, sudo provides a -v flag (short for --validate) that refreshes the credentials without actually running a command.

    So, you need to arrange to run sudo -v every, say, 10 minutes while your Haskell script is running. You can do this by forking a thread that runs forever. Something like the following ought to work:

    import System.Process
    import Control.Monad
    import Control.Concurrent
    
    main = do
      forkIO $ forever $ do               -- do this forever
        threadDelay 600000000             -- wait 10 minutes
        callCommand "sudo -v"             -- refresh credentials
      callCommand "sudo sleep 1200"       -- wait 20 minutes
      callCommand "sudo cat /etc/passwd"
    

    In my test, I was prompted for a password when the program started (i.e., in order to run sudo sleep 1200), but even though that slept for 20 minutes (long enough for the credentials to expire), the sudo -v that ran after only 10 minutes kept the credentials valid, so the sudo cat /etc/passwd command ran without prompting.

    Note that, when the main thread exits, the forked thread is automatically killed. If this was part of a larger program where you wanted to keep credentials alive for a limited amount of time and then allow them to expire while the main program kept running, you'd want store the thread ID of the forked thread and kill it when you were done, like so:

    main = do
      t <- forkIO $ forever $ do          -- save the ID of the thread
        threadDelay 600000000
        callCommand "sudo -v"
      callCommand "sudo sleep 1200"
      callCommand "sudo cat /etc/passwd"
      killThread t                        -- stop refreshing credentials
      -- ...
      -- do more stuff, without refreshing credentials
      -- ...