Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the authentication / sign-on story #30

Closed
rdaum opened this issue Sep 28, 2023 · 6 comments
Closed

Improve the authentication / sign-on story #30

rdaum opened this issue Sep 28, 2023 · 6 comments

Comments

@rdaum
Copy link
Owner

rdaum commented Sep 28, 2023

LambdaMOO cores' connect function uses a variant of the Unix crypt function, ala old Unix passwd files, and stores the passwords in-db.

For compatibility with existing cores (and to make servicing existing telnet clients feasible), it's necessary to keep this process for now. But this isn't going to cut it as a security story.

Some kind of SSO story & auth token is going to be needed. For the latter, one option is JWT, another is PASETO, which sounds more promising.

Then the websocket connection (and any HTTP sessions generally) will need to be authenticated with this token before client connections can proceed.

The long-term future for the telnet modality will need to be thought about in this context.

Not a requirement for 1.0 (LambdaMOO compatible) release.

@jaybird110127
Copy link

I would think that for now, you should be able to replace the underlying crypt() builtin with some sort of hash, and have that builtin ignore any salt passed to it. If I understand the way the whole thing works, this should still work, without modifying any MOO code. The code crypts a password, gets a hash back, stores that, then when it needs to check it, it crypts the supplied password, also supplies the first two chars as salt (which a new crypt builtin would ignore) and gets a hash back, and if they match, we're good.

@rdaum
Copy link
Owner Author

rdaum commented Sep 28, 2023

You're probably right it would be possible to make it work that way, but I'm still not convinced that having passwords in-core is the right story at all... and I'm pretty sure what we're describing here is not good security practice in this day and age.

And there's no reason in this day one couldn't support e.g. OAuth2, with just tying the 'player' object ID to external credentials, and rely on other, better-structured/audited, etc. security services to carry the weight & risk.

@jaybird110127
Copy link

All this is true. However, if you did make the crypt() builtin hash instead of encrypt, you could have an unmodified LambdaCore, right out of the gate, that doesn't store passwords (not even crypted ones) in the database.

@pppery
Copy link

pppery commented Oct 4, 2023

The code crypts a password, gets a hash back, stores that, then when it needs to check it, it crypts the supplied password, also supplies the first two chars as salt (which a new crypt builtin would ignore) and gets a hash back, and if they match, we're good.

The code actually supplies the entire crypted password as the salt (even though the documentation for crypt() says not to do that), and the crypt() function itself is responsible for reducing it to two characters. Which I guess means you could have crypt() implement whatever the current standard for password hashing is and there's no reason that wouldn't cut it.

Personally I don't understand the arguments against storing passwords in-database - my proposed scheme is basically identical to the way that any other login-requiring website works. I thought the idea of MOO was to do as little as possible in-driver and as much as possible in-core.

rdaum added a commit that referenced this issue Nov 1, 2023
All RPC requests to the daemon now have attached Client and Auth
PASETO tokens. The former is for validating that the RPC connection is
who it says it is. The latter is for validating that the user is who
they say they are.

It is now possible to get an auth token from logging in through
`/ws/auth` endpoint. Though there's nothing yet in place to use it.

The idea is that websocket connections (and HTTP generally as well)
will work this way:

1) auth and get a PASETO token for the player that is auth'd to.

2) subsequent calls to the system -- such as a websocket attach, or a
request to retrieve a property or verb, etc.) will attach the auth
token in a `X-Moor-Auth-Token` header on each request.

TODO:

  * validate the player inside the Scheduler/Task layer before
    launching a task (and return E_PERM etc.)

  * add a websocket connect that uses the authorization header to skip
    login, and remove the Basic-Auth (which can't work from a browser
    now anyways)

(#30)
@Karrtaan
Copy link

Hi. Looking deeply into this, I wonder why not using SSH instead? While most clients are very well able to connect via telnet, they cannot utilize all the neat features of SSH, yes, for that it would be necessary to build some sort of local proxy to translate from the outbound SSH-connection to an internal telnet connection, but this way all features of SSH could be used, including public key encryption, SSO etc. Using this, I would guess it is possible to use the same SSO-backend for both http and pseudo-telnet connections. Just some thoughts from a user perspective. Yes, it breaks compatibility with telnet, on the other side it solves almost all security issues. Telnet is a security risk by itself anyway.

@rdaum
Copy link
Owner Author

rdaum commented Jan 7, 2024

Hi. Looking deeply into this, I wonder why not using SSH instead?

Sorry to not reply to this for a while. I have been busy with real, paid, work, and haven't had time to give to moor in a while.

But to update, what's actually happened here is that I've chosen to prioritize the web/websockets story over everything else. The expected primary mode of interaction with the moo hosted in moor is to be over HTTP and websockets (and, likely, in the future webtransport). The line-command oriented style of LambdaMOO is supported over websockets (which are kinda like 21st century "telnet", really), but verb invocations, property access, and eval are also supported over (authenticated) HTTP GET/POST.

And because of that what I ended up implementing for authorization is the following:

in web-host, HTTP authorization is established through two URLs (/ws/connect and /ws/create) which take HTTP basic auth (presumably only over an HTTPS connection). They then contact the moor backend (daemon) over ZMQ RPC, do standard MOO password authentication (salted hash but just for now) and then establish PASETO auth tokens. (PASETO is a little bit like jwt but better. )

Then the actual sessions -- either websocket, or HTTP -- have to provide a valid PASETO token for each connection. For the websocket, this is passed as a query string parameter before the websocket upgrade. For HTTP POST/GET it is passed in HTTP headers. (Browsers unfortunately do not support custom HTTP headers for websocket connections, which boggles my mind).

This was all implemented back a couple months ago, along with a primitive web client that lets you connect, authenticate, does the command loop over the websocket, but also has a prototype (but incomplete) Smalltalk-style object browser and syntax highlight MOO verb code editor (using the same editor that drives VSCode).

But I have not had time to write it up and document it yet. I still consider the last bit prototype phase.

@rdaum rdaum closed this as completed Jan 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants