Skip to content

Instantly share code, notes, and snippets.

@KellerFuchs
Last active September 11, 2016 17:22
Show Gist options
  • Save KellerFuchs/3ccdebe1fa466f55ffb5 to your computer and use it in GitHub Desktop.
Save KellerFuchs/3ccdebe1fa466f55ffb5 to your computer and use it in GitHub Desktop.
#!ctl protocol

Rationale

Older versions of hashbangctl(1) used to be called through sudo(8), with all users being allowed to call that command without password.

This is not optimal, both for security reasons and because it would make it very complex for hashbangctl(1) to outlast the user's session (for instance, in the case of user deletion).

Alternatives

The main alternatives considered were ØMQ and DBus.

  • DBus is overly complex, and while making a set of Python methods available would not be a challenge, secure (including race-free) authentication was.
  • ØMQ would only provide framing (its existing authentication protocol was not suitable for our needs), and makes it impossible to use SO_PEERCRED as the API does not expose the underlying socket.

Authentication

Users are authenticated using the SO_PEERCRED socket option, which returns a structure containing the user's id (and groups ids).

This was chosen because:

  • it doesn't require cooperation from the client, making it very easy to implement primitive clients (in a shell script, for instance);
  • it is simple for the server to implement, leaving very little room for error;
  • the credentials are provided by the kernel itself, making them impossible to spoof, except by root.

NOTE: Administrators can use sudo -u someuser hashbangctl to impersonate a user. This was hard to cleanly do in the previous design.

Alternatives

  • SCM_CREDENTIALS allows sending kernel-checked credentials through a Unix domain socket; it requires explicit cooperation from the client, for no specific benefit (in our usecase).
  • The python-dbus library does not provide way to obtain the sending user without suffering from a race-condition that could enable attackers to impersonate other users.

Wire format

Encoding

Messages are encoded in I-JSON, a fragment of JSON that removes many ambiguities from the original specification.

As such, any standard-conformant JSON implementation can reliably parse and interpret data encoded using I-JSON.

In particular, I-JSON messages MUST be encoded using UTF-8.

Framing

The socket type SOCK_SEQPACKET is used to provide reliable, in-order delivery of datagrams: this means the operating system already handles framing for us.

Protocol

The protocol works on a simple request/response model:

  1. Upon socket connection, the daemon sends a record describing the user (name, pubkeys, ...).
  2. The frontend can send back a request, identified by its type field:
    • change denotes a request to update the information in LDAP; fields (name, pubkey, ...) that are not filled in are taken from LDAP;
    • delete is a request to delete the requesting user; no additional information is required.
  3. The daemon respond with a type that is either success (without associated information) or error (with an optional msg string field).
  4. From there on, the client can submit a new request (back to 2).

NOTE: The client can disconnect at any point; SOCK_SEQPACKET guarantees reliable, atomic message delivery.
For example, sending {"type": "delete"} and exiting without waiting for the response is valid.

@KellerFuchs
Copy link
Author

Removed type = delete, that's a terrible idea (much better to disable an account by setting it to nologin).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment