This is a rough specification of the protocol used by privleap. It is intended to give a developer an idea of what IPC protocol used by privleap looks like. It does not, however, answer every possible question one could have about the format. As privleap is believed to be the only implementation of this protocol, this is not expected to be a practical issue.
- 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 using an unsigned 4-byte integer for the length prefix. 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.
The format of each message is as follows:
<msg_name> <arg_count> [<arg_1> ... <arg_63>] [<binary_blob>]
Where:
<msg_name>is the name of the message in question. It must consist entirely of non-whitespace 7-bit ASCII characters. It must be terminated by a space (ASCII0x20).<arg_count>is a single Base64-like digit representing the number of arguments being included with the message. If the digit is not0, or if a binary blob is included with a message, the count must be terminated by a space, otherwise there must not be a terminating space..- The characters
0-9are used to represent values 0 through 9. The charactersA-Zare used to represent values 10 through 35. The charactersa-zare used to represent values 36-61. The characters+and/are used to represent values 62 and 63. This uses the same characters as true Base64, but in a more intuitive order that is not any more difficult to parse.
- The characters
<arg_1> ... <arg_63>represents the argument list. Each argument must consist entirely of non-whitespace 7-bit ASCII characters. Arguments that are not the last argument must be terminated by a space. For messages that include a trailing binary blob, the last argumet must be terminated by a space as well, otherwise there must not be a terminating space. There must be exactly as many arguments as specified by the argument count field.<binary_blob>represents arbitrary binary data. If it is supported by a message, it must be present, otherwise it must be absent. If it is present, it will occur immediately after the terminating space of the last argument (or immediately after the terminating space of the argument count if the argument count is 0).
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.
privleapd understands the following messages passed via the control socket:
CREATE- Creates a communication socket for a specified user. Supports one mandatory argument, the name of the user to make a communication socket for. Does not include a binary blob. Must be sent as the first message in the session.- For instance, to create a user socket for account
john, the messageCREATE 1 johnwould be sent by the client.
- For instance, to create a user socket for account
DESTROY- Destroys a communication socket for a specified user. Supports one mandatory argument, the name of the user to destroy the communication socket of. Does not include a binary blob. Must be sent as the first message in the session.- For instance, to destroy a user socket for account
john, the messageDESTROY 1 johnwould be sent by the client.
- For instance, to destroy a user socket for account
RELOAD- Reloads configuration data. Supports no arguments. Does not include a binary blob. Must be sent as the first message in the session.
privleapd can send the following messages on the control socket, which
clients must be able to understand:
OK- The requested operation succeeded. Supports no arguments. Does not include a binary blob. May be sent as a reply toCREATE,DESTROY, orRELOAD.CONTROL_ERROR- The requested operation failed. Does not include a binary blob. Supports no arguments. May be sent as a reply toCREATE,DESTROY, orRELOAD.EXISTS- The user specified by the previous command already has a communication socket open. Supports no arguments. Does not include a binary blob. May be sent as a reply toCREATE.NOUSER- The user specified by the previous command does not have a communication socket open. Supports no arguments. Does not include a binary blob. May be sent as a reply toDESTROY.PERSISTENT_USER- The user specified by the previous command is a "persistent user" and cannot be destroyed. Supports no arguments. Does not include a binary blob. May be sent as a reply toDESTROY.- See
man privleap.conffor more information on persistent users.
- See
DISALLOWED_USER- The user specified by the previous command is not an "allowed user" and cannot have a socket created for them. Supports no arguments. Does not include a binary blob. May be sent as a reply toCREATE.- See
man privleap.conf.dfor more information on allowed users.
- See
EXPECTED_DISALLOWED_USER- The user specified by the previous command is an "expected disallowed user" and cannot have a socket created for them. Supports no arguments. Does not include a binary blob. May be sent as a reply toCREATE.- See
man privleap.conf.dfor more information on allowed users.
- See
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 1 <username> when <username> logs in, while an application involved
in user logout is expected to send DESTROY 1 <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 1 <username>, while leapctl --destroy <username> can
be used to send DESTROY 1 <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, which in turn allows
privleapd to identify which user is sending messages to it.
privleapd understands the following messages passed via a communication
socket:
SIGNAL- Sends a signal toprivleapd, requesting the triggering of an action. Supports one mandatory argument, the name of the signal to send. Does not include a binary blob. Must be sent as the first message in the session.- For instance, to trigger an action named
do-thing, the messageSIGNAL 1 do-thingwould be sent by the client.
- For instance, to trigger an action named
ACCESS_CHECK- Queriesprivleapdto determine if the caller is authorized to trigger the batch of actions corresponding to the signal names given as arguments. Supports up to 63 arguments, with only one argument being mandatory. Does not include a binary blob. Must be sent as the first message in the session.- For instance, to ask the server if the client may run the actions
do-thing,do-other, andwhatnot, the messageACCESS_CHECK 3 do-thing do-other whatnotwould be sent by the client.
- For instance, to ask the server if the client may run the actions
TERMINATE- Instructsprivleapdto immediately terminate the running action and cease sending process output to the client. Supports no arguments. Does not include a binary blob. May only be sent after receiving aTRIGGERmessage at some prior point in the session.
privleapd can send the following messages, which clients must be able to
understand:
TRIGGER- Indicates that the action requested by the last message has been triggered and is now mid-execution. Supports no arguments. Does not include a binary blob. May be sent as a reply toSIGNAL.TRIGGER_ERROR- Indicates that the client was authorized to trigger the action requested by the last message, but that the action could not be executed. Supports no arguments. Does not include a binary blob. May be sent as a reply toSIGNAL.- Note that this message is reserved for issues actually starting the
action. If the action can be executed but exits with a non-zero exit code,
clients should expect to receive
TRIGGER, followed by aRESULT_EXITCODEmessage at some later point in the session.
- Note that this message is reserved for issues actually starting the
action. If the action can be executed but exits with a non-zero exit code,
clients should expect to receive
RESULT_STDOUT- Transfers a block of data the action wrote to its stdout stream to the client. Supports no arguments. Includes a binary blob, which is the block of stdout data. May be sent at any time after sendingTRIGGER, but must not be sent after the server has processed aTERMINATEmessage from the client or after the server has sentRESULT_EXITCODE.RESULT_STDERR- Identical toRESULT_STDOUT, but the block of data comes from the action's stderr stream.RESULT_EXITCODE- Indicates that the action requested by a priorSIGNALmessage has completed execution, and provides its exit code to the client. Supports one mandatory argument, a string consisting entirely of ASCII digits that together represent an integer between 0 and 255 inclusive. Does not include a binary blob. May be sent at any time after sendingTRIGGER.- For instance, if the action exited with code
42, the messageRESULT_EXITCODE 1 42would be sent by the server.
- For instance, if the action exited with code
AUTHORIZED- Indicates that the client is authorized to trigger the actions specified in the message. Supports up to 63 arguments, with only one argument being mandatory. Does not include a binary blob. May be sent at any time after recievingACCESS_CHECK.- For instance, to tell the client that it is authorized to run actions
do-thinganddo-other, the messageAUTHORIZED 2 do-thing do-otherwould be sent by the server.
- For instance, to tell the client that it is authorized to run actions
UNAUTHORIZED- Indicates that the user client's user account is not authorized to trigger the actions specified in the message, either because the actions exists but are not permitted for the user, or because the actions do not exist at all. Supports up to 63 arguments, with only one argument being mandatory. Does not include a binary blob. May be sent in reply toSIGNAL, or at any time aftrer recievingACCESS_CHECK.- For instance, to tell the client that is it not authorized to run the
action
whatnot, the messageUNAUTHORIZED 1 whatnotwould be sent by the server. - Note that if an
UNAUTHORIZEDmessage is sent as one of the responses to anACCESS_CHECKmessage, it is not expected to be the last message from the server in the session. AnACCESS_CHECK_RESULTS_ENDmessage is expected after it. However, when sent as a response to aTRIGGERmessage,UNAUTHORIZEDis expected to be the last message from the server.
- For instance, to tell the client that is it not authorized to run the
action
ACCESS_CHECK_RESULTS_END- Indicates that the server is done sending access check results. Supports no arguments. Does not include a binary blob. May be sent at any point after anACCESS_CHECKis received and one or both ofAUTHORIZEDandUNAUTHORIZEDhas already been sent.
privleapd will only parse the first one of each of the above client-sent
messages per session. When processing a TRIGGER message, privleapd will
exit immediately after sending an UNAUTHORIZED RESULT_EXITCODE message.
When processing an ACCESS_CHECK message, privleapd may exit after sending
one or both of AUTHORIZED and UNAUTHORIZED.
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 a
SIGNALmessage is sent,privleapdwill respond with eitherTRIGGERif the client is authorized to trigger the action, orUNAUTHORIZEDotherwise. IfTRIGGERis sent, the server will will sendRESULT_STDOUTandRESULT_STDERRmessages to stream the stdout and stderr output from the action to the client, then sentRESULT_EXITCODEonce the action finishes to send the exit code to the client. - If an
ACCESS_CHECKmessage is sent,privleapdwill respond with anAUTHORIZEDmessage listing all specified actions the client is authorized to trigger, and anUNAUTHORIZEDmessage listing all specified actions the client is not authorized to trigger. It will then send anACCESS_CHECK_RESULTS_ENDto signal to the client that the results are done and the server is about to disconnect.
After the appropriate actions are taken, privleapd will close the
connection.
- 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. - It may be useful to allow streaming stdin from the client to the server eventually. This would have to be done carefully to prevent too much attack surface from being present, potentially requiring some method of server-side filtering of client-provided stdin data.