pyceo/architecture.md

71 lines
3.9 KiB
Markdown

# Architecture
ceo is a distributed HTTP application running on three hosts. As of this
writing, those are phosphoric-acid, mail and caffeine (coffee in the dev
environment).
* The `mail` host provides the `/api/mailman` endpoints. This is because
the REST API for Mailman3 is currently configured to run on localhost.
* The `caffeine` host provides the `/api/db` endpoints. This is because
the root account of MySQL and PostgreSQL on caffeine can only be accessed
locally.
* All other endpoints are provided by `phosphoric-acid`. phosphoric-acid is the
only host with the `ceod/admin` Kerberos key which means it is the only host
which can create new principals and reset passwords.
Some endpoints can be accessed from multiple hosts. This is explained more in
[Security](#security).
Interestingly, ceod instances can actually make API calls to each other. For
example, when the instance on phosphoric-acid creates a new user, it will
make a call to the instance on mail to subscribe the user to the csc-general
mailing list.
## Security
In the old ceo, most LDAP modifications were performed on the client side,
using the client's Kerberos credentials to authenticate to LDAP via GSSAPI.
Using the client's credentials is desirable since we currently have custom
authz rules in our slapd.conf on auth1 and auth2. If we were to use the
server's credentials instead, this would result in two different sets of
authz rules - one at the API layer and one at the OpenLDAP layer - and
syscom members would very likely forget to update both at the same time.
So, we want a way for the server to use the client's credentials when
interacting with LDAP. The most secure way to do this is via a Kerberos
extension called "constrained delegation", or [S4U](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/1fb9caca-449f-4183-8f7a-1a5fc7e7290a).
While the MIT KDC, which we are currently using, does provide support for S4U,
this [requires using LDAP as a database backend](https://k5wiki.kerberos.org/wiki/Projects/ConstrainedDelegation#CHECK_ALLOWED_TO_DELEGATE),
which we are *not* using. While it is theoretically possible to migrate our
KDC databases to LDAP, this would be a very risky operation, and probably
not worth it if ceo is the only app which will use it.
Therefore, we will use unconstrained delegation. The client essentially
forwards their TGT to ceod, which uses it to access other services over GSSAPI
on the client's behalf. We accomplish this using GSSAPI delegation (i.e. set
the GSS_C_DELEG_FLAG when creating a security context).
Since the client's credentials are used when interacting with LDAP, this means
that most LDAP-related endpoints can actually be accessed from any host.
Only the Kerberos-specific endpoints (e.g. resetting a password) truly need
to be on phosphoric-acid.
### Authentication
The REST API uses SPNEGO for authetication via the HTTP Negotiate
Authentication scheme (https://www.ietf.org/rfc/rfc4559.txt). The API
does not verify that the user actually knows the key for the service ticket;
therefore, TLS is necessary to prevent MITM attacks. (TLS is also necessary
to protect the KRB-CRED message, which is unencrypted.)
SPNEGO is pretty awkward, to be honest, as it completely breaks the stateless
nature of HTTP. If we decide that SPNEGO is too much trouble, we should switch
to plain HTTP cookies instead, and cache them somewhere in the client's home
directory.
## Web UI
For future contributors: if you wish to make ceo accessible from the browser,
you will need to add some kind of "Kerberos gateway" logic to the API such
that the user's password can be used to obtain Kerberos tickets. One possible
implementation would be to prompt the user for a password, obtain a TGT,
then encrypt the TGT and store it as a JWT in the user's browser. The API
can decrypt the JWT later and use it as long as the ticket has not expired;
otherwise, the user will be re-prompted for their password.