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

[Request] Add support for password protection #11

Closed
MichaIng opened this issue Nov 6, 2021 · 17 comments
Closed

[Request] Add support for password protection #11

MichaIng opened this issue Nov 6, 2021 · 17 comments
Labels
enhancement New feature or request

Comments

@MichaIng
Copy link
Collaborator

MichaIng commented Nov 6, 2021

Since the DietPi-Dashboard allows server administration and currently runs as root user, it makes sense to protect access. It doesn't run on a common port, so behind a NAT usually port forwarding needs to be enabled, but in other cases like a VPS the port might be publicly accessible OOTB and in other cases you may want to access DietPi-Dashboard remotely (while then I would always recommend a VPN).

Concepts:

  • It should be optional since users may want to implement authentication at a proxy level. I would however enable it by default during DietPi-Software installs, using the global software password.
  • Easy to implement would be probably classic HTTP authentication with username and password, with the known limitation of either unencrypted store or unencrypted transfer (as long as HTTPS is not enabled). I'm pretty sure the used Rust crates already support it natively or another crate can be found quickly which does it.
  • I personally would prefer a simpler solution with password only, but not username. This is an admin interface, hence only a single person should have access. This is however not natively supported via HTTP authentication, hence it would require a custom implementation, or a crate which serves it. In theory it could be kept simple: Show an input box, take the input and hash/encrypt it, then compare against the value stored in the config file, which as well contains the hashed/encrypted password only. The limitation/problem is the same then, when we use encryption or hashing with salt: Either the password needs to be sent unencrypted and encrypted server-side for comparison, which is a problem on plain HTTP, or the password cannot be encrypted/salted, but only hashed (without salt) client-side and stored like that as well server-side. This is since of course the encryption key or hash salt can only be stored on the server either. If we enforce strict 0600 mode on the config file, I'm actually fine with a sha512 hash only. A more complex compromise would be sha512 hashed transfer, but salted/encrypted storing in the config file. The client then does the hashing only and the server does the salted hashing/encryption with the key/salt stored as well in the config file to have more more secure there. Again another way would be to send the key/salt hashed to the client to encrypt the password, lol this would be a whole own secure password transfer implementation 😄. Probably better to go with known HTTP authentication...
@ravenclaw900
Copy link
Owner

ravenclaw900 commented Nov 10, 2021

How about JWTs? They could be sent with each request in the JSON object, and it seems to be the easiest way with how the dashboard is set up.

@MichaIng
Copy link
Collaborator Author

How is the initial authentication done with this? And how do clients/browsers store and transmit it? Storing it on either local/session storage or a cookie both has security downsides. A common/safe way to transmit them seems to be via Authorization header (or in theory any other custom header), but browser won't do this automatically 🤔. Not sure if something could be done about this via JavaScript?

@ravenclaw900
Copy link
Owner

My thought is that a dialog will be shown, and the password will be entered and sent to the backend. If the password matches, the backend will then generate a token and send it back. The token will then be used in every websocket request until it expires. I was just going to store it in a variable, though that would require logging in every time the page was loaded.

@MichaIng
Copy link
Collaborator Author

I was just going to store it in a variable, though that would require logging in every time the page was loaded.

And reload... I think to not make it nasty, there needs to be some kind of persistent storage at the client. Here is a good overview of options and their possible downsides: https://stackoverflow.com/a/40376819/16145737

  • CSRF prevention is a bit nasty to achieve as long as we don't know all hostnames/IPs this is accessed with. We could implement a config setting to store trusted hostnames/IPs, but much to do...
  • XSS prevention should be fine with strict CSP and classic security response headers.

@ravenclaw900
Copy link
Owner

ravenclaw900 commented Nov 20, 2021

Password protection implemented with 6eff076. New config file parameters:

pass: whether to use password or not
hash: sha512 hash of password
secret: 64-character randomized secret

It currently only works on page changes, I'm working on cleaning it up.

@MichaIng
Copy link
Collaborator Author

Awesome! So you use JWTs now. Just for clarification: The sha512 is an unsalted hash, hence can be generated via sha512sum <<< 'password', the secret is used (with the password) to generate the token which is sent as JWT to the client and stored only in the current session storage, right? Hence similar to light/dark mode, reloading the page currently requires to reenter the password, right?

Will do a quick test.

@ravenclaw900
Copy link
Owner

ravenclaw900 commented Nov 20, 2021

Yes the hash is unsalted, and the secret is used to generate the token, however the token is stored in localStorage, and lasts for an hour before it expires (do you think it should last longer?). Once that happens, you will have to reenter the password.

@MichaIng
Copy link
Collaborator Author

however the token is stored in localStorage

Ah, okay, I didn't see that one. That sounds good!

@ravenclaw900
Copy link
Owner

Alright, everything should be using the token now. Only thing that I can't get to work is the terminal when you have to login. It won't load unless you reload the page.

@MichaIng
Copy link
Collaborator Author

MichaIng commented Nov 20, 2021

https://github.com/ravenclaw900/DietPi-Dashboard/blob/d3b716c/src/frontend/src/App.svelte#L308

Don't all routes require {socketSend}?
And when navigating to the Terminal page, is socketData actually set, so that the condition to get the token from local store is met? https://github.com/ravenclaw900/DietPi-Dashboard/blob/d3b716c/src/frontend/src/App.svelte#L104

@ravenclaw900
Copy link
Owner

ravenclaw900 commented Nov 20, 2021

The terminal uses a separate websocket /ws/term instead of /ws, so it doesn't use socketSend. socketData.update/login is a special "global" variable sent when the websocket is first loaded, and then saved.

@MichaIng
Copy link
Collaborator Author

I thought that might be the reason as socketSend creates the JSON with the token. I see the dedicated socket creation in Terminal.svelte now and the last commit. What is the reason for it to use this separate socket?

socketData.update/login is a special "global" variable sent when the websocket is first loaded

Ah I see it, and also not relevant for the terminal socket handler.

@ravenclaw900
Copy link
Owner

Mainly because Xterm uses its own attachment mechanism, which just sends data back and forth, and doesn't JSON encode anything, which is what the main socket is designed for.

@MichaIng
Copy link
Collaborator Author

MichaIng commented Nov 20, 2021

Ah I see. I guess it would be more overhead when using a /ws socket for token validation only and the /ws/term socket for the embedded terminal only, compared to the double validation code? But probably the validation itself can be done in a shared function?

@ravenclaw900
Copy link
Owner

ravenclaw900 commented Nov 20, 2021

Good idea, though the /ws socket is always connected anyway. However, if the socket(s) were to be used as e.g. an API to the DietPi system, having the terminal completely independent is a good idea.

@ravenclaw900
Copy link
Owner

Shared function added with 6603a7f.

@ravenclaw900
Copy link
Owner

Since password protection has been implemented, I'm going to close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants