This is the specification for the privleap protocol. Ambiguities are to be considered bugs, please report them!
- Action - A set of commands and accompanying metadata defined in one of privleap's configuration files. Each action corresponds to a single line of Bash code that is run when the action is triggered. Actions can only be triggered by users and groups authorized to trigger them and may require the person operating the authorized user to prove their identity. Each action has a specific name.
- Session - A connection between a privleap client and a privleap background process. One or more messages may be sent back and forth in a session.
- Message - A single unit of data passed to privleap by an application, or passed to the application from privleap.
- Signal - A message sent to privleap specifying a particular action the application wishes to trigger. Each action has a corresponding signal with the same name as the action.
The server should assume that user-level clients are malicious and therefore treat any data from them with the utmost caution. Additionally, the server should aim to prevent DoS attacks caused by resource-intensive activity patterns. In general, the server should err on the side of caution and terminate a connection without explanation if the client is not sending data quickly enough or sends unexpected data. This same level of care does not need to be taken with the control socket, as it is accessible only by root and thus is not a danger.
Each message is formatted as a Pascal-style string that can be up to 2^32-1 bytes long. The length prefix is in big-endian byte order. The string may contain arbitrary binary data (including NUL bytes) and should not be blindly trusted if it comes from an untrusted source. Messages are case-sensitive.
Throughout this spec, placeholders are formatted as <placeholder>. These
placeholders are expected to be substituted wholesale with the actual data that
belongs there. For instance, CREATE <username> contains the placeholder
<username>. If the username john is being used, the string would be
changed to CREATE john.
When an application connects to any socket privleapd has open and is
listening on, it begins a session with privleapd. Sessions are only held
open for as long as necessary, and are closed as soon as possible.
On startup, privleapd creates and listens on a socket at
/run/privleapd/control. This socket is owned by root:root and uses
permissions 0600. The containing directory /run/privleapd is owned by
root:root and uses permissions 0644. The control socket is used to
manage communication sockets for each individual user account.
Any messages sent by the client must be no longer than 4096 bytes (not
including the four-byte message length header). privleapd will forcibly
disconnect a client that attempts to send a longer message than this. This is
expected to be enough to allow any practically-sized usernames, signal names,
and authentication data to be passed now and in the future. The server may send
messages longer than this, although it is not generally recommended.
privleapd understands the following messages passed via the control socket:
CREATE <username>- Creates a communication socket for the specified user.privleapdwill respond withOKif this succeeds,EXISTSif the socket already exists, andCONTROL_ERRORif this fails.DESTROY <username>- Destroys a communication socket for the specified user.privleapdwill respond withOKif this succeeds,NOUSERif the specified user does not have a communication socket, andCONTROL_ERRORif this fails.RELOAD- Reloads configuration data.privleapdwill respond withOKif this succeeds, andCONTROL_ERRORif this fails (due to invalid configuration). The details of the invalid configuration that led to a failure will be logged.
privleapd can send the following messages on the control socket, which
clients must be able to understand:
OK- The operation specified by the first client-sent message (eitherCREATEorDESTROY) in this session succeeded.CONTROL_ERROR- The operation specified by the first client-sent message (eitherCREATEorDESTROY) in this session failed.EXISTS- The first client-sent message was aCREATEmessage, but the username specified in the message already has a communication socket open.NOUSER- The first client-sent message was aDESTROYmessage, but the username specified in the message has no communication socket open.PERSISTENT_USER- The first client-sent message was aDESTROYmessage, but the username specified in the message is a "persistent user" and cannot have their socket destroyed. (See /etc/privleap/conf.d/README for more information on persistent users.)DISALLOWED_USER- The first client-sent message was aCREATEmessage, but the username specified in the message is not an "allowed user" and cannot have a socket created for them. (Seeman privleap.conf.dfor more information on allowed users.)EXPECTED_DISALLOWED_USER- The first client-sent message was aCREATEmessage, but the username specified in the message is an "expected disallowed user" and cannot have a socket created for them. (Seeman privleap.conf.dfor more information on allowed users.)
Regardless of the reply, privleapd will close the session immediately after
sending one of the above messages, and will ignore all but the first message
the client sends.
In actual operation, an application involved in user login is expected to send
CREATE <username> when <username> logs in, while an application involved
in user logout is expected to send DESTROY <username> when the user logs
out. This ensures that only actively logged-in users have open communication
sockets. If using shell scripts for this, leapctl --create <username> can be
used to send CREATE <username>, while leapctl --destroy <username> can be
used to send DESTROY <username>.
Communication sockets are stored under /run/privleapd/comm. Each
communication socket is named after the user it is intended to be used by
(i.e. <username>). It is owned by the user and group corresponding to that
user, and uses permissions 0600. This ensures that only the authorized user
can communicate with privleapd over this socket. This allows privleapd to
identify which user is sending messages to it.
privleapd understands the following messages passed via a communication
socket:
SIGNAL <signal_name>- Sends a signal toprivleapd, requesting the triggering of the action corresponding to<signal_name>.ACCESS_CHECK <signal_name>- Queriesprivleapdto determine if the caller is authorized to trigger the action corresponding to<signal_name>.TERMINATE- Instructsprivleapdto immediately terminate the running action. The server should not attempt to send any further messages to the client once this message is received.
privleapd can send the following messages, which clients must be able to
understand:
TRIGGER- Indicates that the action requested by the previousSIGNALmessage has been triggered.privleapdwill sendTRIGGERafter beginning execution of the code specified in the action, with no respect to whether the action has completed or not. If a client is satisfied that the action has started execution, it can disconnect fromprivleapdimmediately; otherwise, it should wait to disconnect until receiving aRESULT_EXITCODEmessage.TRIGGER_ERROR- Indicates that the action requested by the previousSIGNALmessage was authorized, but launching it failed. This is usually due to invalid or faulty configuration. Note thatTRIGGER_ERRORis reserved for actual problems starting the action. If the action starts successfully but exits non-zero,privleapdwill still consider this a success. The client can use the value accompanying theRESULT_EXITCODEmessage to determine whether the action actually succeeded or ignore it.RESULT_STDOUT <result_stdout_text>- Indicates that the action requested by the previousSIGNALmessage wrote the given block of data to STDOUT.<result_stdout_text>is arbitrary binary data.RESULT_STDERR <result_stderr_text>- Indicates that the action requested by the previousSIGNALmessage wrote the given block of data to STDERR.<result_stderr_text>is arbitrary binary data.RESULT_EXITCODE <exit_code>- Indicates that the action requested by the previousSIGNALmessage has completed execution, and that it exited with code<exit_code>.<exit_code>is a string representing a decimal integer between 0 and 255.AUTHORIZED- Indicates that the user who sent anACCESS_CHECKquery is authorized to run the action specified by theACCESS_CHECKmessage.UNAUTHORIZED- Indicates that the user attempting to trigger or query the specified action is not authorized to trigger that action, or that the signal sent by the client has no matching action. This response intentionally does not tell the client whether they simply aren't allowed to perform the action, or whether the action doesn't even exist.
privleapd will only parse the first one of each of the above client-sent
messages per session. privleapd will terminate the session immediately after
sending AUTHORIZED, UNAUTHORIZED, or RESULT_EXITCODE. privleapd
may send multiple RESULT_STDOUT and RESULT_STDERR messages in a
single session.
In actual operation, the client (most likely leaprun) is expected to open a
session with privleapd and send either a SIGNAL or ACCESS_CHECK message.
If the user is authorized to trigger the action specified in the message,
privleapd will respond with either AUTHORIZED in reply to an
ACCESS_CHECK, or TRIGGER in response to a SIGNAL. If privleapd is
running an action, it will send RESULT_STDOUT and RESULT_STDERR messages
as the action runs so the caller can see the output of the action.
- Right now, privleap is designed only to allow running specific actions as
root without a password, without the dangers of sudo. It would be much
more useful if it were able to handle identity verification. This could be
done with
CHALLENGEandCHALLENGE_PASSmessages from the server, and aRESPONSEmessage from the client. This would only allow for simple password-based (or similar) authentication, though. PAM could potentially also be used, but trying to forward PAM auth requests over a socket could be extremely difficult or impossible, so in practice this would probably end up bypassing PAM, which is potentially livable, but not ideal.