EGBOK Consultants

Sudoscript 2.0 Architecture


 

Introduction

 

The original sudoscript architecture was a simple response to the problem of allowing software engineers to run root shells on their workstations. Generally, these users would be the only ones running sudoshell on their machine. This was fortunate, because the original architecture made no allowance for multiple simultaneous users. There was a single logging FIFO and a single log file. Multiple users could invoke sudoshell, but all their output would be thrown together in the log file, without any separation of the various sessions. As a practical matter, user input could usually be differentiated by looking at the prompt. But since PS1 is settable by the user, and therefore varies quite a bit, it couldn't be counted on to allow such differentiation.

 

Tommy Smith's Idea

 

Some mechanism was required to allow session differentiation. I got an email from Tommy Smith suggesting that separate log files could be used to allow identification of sessions. This struck me as a very good idea. I started to think about how that could be implemented. Forking a logging daemon per session seemed like it would be fairly easy. I had a prototype doing that pretty quickly. About the same time, I was exploring an idea from Adam Morris that would get rid of sudoscriptd altogether, and replace in with a forked FIFO server in sudoshell. I took a look at his C code that implemented this idea, and tried to replicate it in Perl. I ran into difficulty with keeping the terminal session attached to script(1). Regardless of whether I implemented the daemon in the parent or the child, I couldn't keep stdin/stdout attached to the 'system script' call. That, and 22 Dell 1650 servers landing on my desk stopped my experiments for a time.

 

The Second Idea

 

When I revisited the code, I decided that I didn't want to get rid of the daemon, because it allowed me to manage the size of the logs produced. Using script(1) meant that large quantities of data, far in excess of that produced by sudo, could be produced by sudoscript. Though I couldn't predict how much data a particular user might produce, I was concerned about overflowing logging partitions. In the 1.0 architecture, the singular daemon kept an eye on the log file. With multiple daemons, I realized, this job would be a lot more complicated. I thought through a couple of schemes that had the front-end daemon orchestrating log file rotation among the back-end loggers. The main difficulty was that the daemon in a position to know about all outstanding sessions didn't "own" the log files. This made it harder to ensure a timely rotation so that data wasn't dropped, while making it easier to miss logs that needed compressing because a back-end daemon had shut down abnormally. I decided that the whole deal was a threat to overall reliability of sudoscript.

I then got the idea of merging all the session data back into another daemon. This daemon would manage a merger FIFO, and coalesce all session data, pre-tagged with a session ID, back into a single log file. The daemon could then manage the log in the same way as the 1.0 daemon did. The overall architecture was significantly more complicated, but it looked to me like it could be reliable if properly implemented. And there were no frills, either. All the complexity had a specific purpose that no simpler architecture I could think of would achieve.

I also decided that I would use syslog to track session startup and shutdown. A short digression regarding syslog is in order here. Since I couldn't control the possibly large quantity of data sudoscript would log, I was reluctant to use syslog for the script(1) data itself. When managing my own log, I was able to trade off between longevity and space in a way that made sense to me. But if I was using syslog, I could end up imposing that trade off on a system log file, where it might be less appropriate. For example, sudoscript 2.0 uses the AUTHPRIV syslog facility. On Red Hat Linux, the default syslog.conf puts messages with this facility into /var/log/secure. If sudoscript were to log all of the script(1) data through to /var/log/secure, then that file would have to turn over frequently. This could flush other important security data out of the system more quickly than appropriate. Also, the sheer quantity of data, combined with the ugliness of script(1) output, would make these logs harder to read. I could use a different facility for the data, but another feature of syslog makes me hesitate to use it. At many sites, particularly those that are Sun based, remote loghosts are employed. I think it's a really bad idea to log by-definition sensitive data over the network in the clear. The large quantity is a factor here as well. Given these considerations, I have decided to stick with a private log file.

 

The 2.0 Architecture

 

I have a rather large GIF image of the 2.0 architecture. If you open this image in a separate window, it might help you understand the explanations that follow.

When sudoscriptd starts, it opens a FIFO called "/var/run/sudoscriptd/rendevous". It then forks a child. The parent process is the sudoscript "master daemon." The child process is the "merger daemon". It opens a FIFO called "/var/run/sudoscriptd/merge". It also opens the log file, "/var/log/sudoscript", for append. The merger goes into a read loop on the merge FIFO. As data appears on this FIFO, the merger checks the size of the log file and forks a rotator/compressor if the size exceeds 2 MB. Meanwhile, the master daemon has gone into a read loop on its FIFO.

When sudoshell starts, it contacts the master daemon over the rendezvous FIFO. It sends a string like "HELO hbo 12345". The latter two fields are the username that invoked sudoshell (taken from the sudo environment, if sudoshell was run, or ran itself with sudo, or just 'root' if it was invoked with privilege) and sudoshell's own process ID. Sudoshell then installs a SIGHUP handler and calls Posix::pause to go to sleep. The master daemon spawns a logger for the sudoshell session. This child opens the merge FIFO for write, and creates a session FIFO using the username and PID derived from the HELO string. It then signals the PID given with a SIGHUP, and goes goes into a loop reading the session FIFO, tagging the data received with the username and PID and writing it to the merge FIFO. Sudoshell wakes up when it receives the SIGHUP, and invokes script(1) on the session FIFO, whose name it derives from its own username and PID.

When the user exits from script(1), sudoshell sends another string to the frontend daemon, identical to the first, except that "HELO" is replaced with "GDBY". Sudoshell then exits. When the master daemon receives the GDBY message, it looks up the session in a table it maintains. It uses the child PID stored there to signal the session daemon with a SIGHUP. It then removes that session from its table. The session daemon cleans up and exits when it receives this signal.

When the master daemon receives a SIGHUP, it loops through its session table and signals each associated logger with a SIGHUP. It then does the same for the merger daemon.

 

Conclusion

 

So, that's the complete life-cycle of the 2.0 sudoscript system. As noted above, important events are logged to syslog with facility AUTHPRIV. This provides a concise record of session activity, which can be used to search the larger log file for particular sessions.

 

 


Document Maintainer: Howard Owen (hbo@egbok.com) Last Updated 6/22/03 5:56 PM