71 lines
3.9 KiB
Markdown
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. The TGT is formatted as a KRB-CRED message,
|
||
|
base64-encoded, and placed in an HTTP header named 'X-KRB5-CRED'.
|
||
|
|
||
|
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.
|