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

Verify nicks #424

Closed
wants to merge 2 commits into from
Closed

Verify nicks #424

wants to merge 2 commits into from

Conversation

Natorator
Copy link

This pr is an alternative implementation of a feature to reserve chat nicks. It is more conservative, simpler, does fewer things, and makes fewer changes to the code base than previous prs #420 and #423 . Thanks to @shazow for many good points of advice from the previous pr thread.

The first commit only loads ssh key comments onto the Auth object. Key comments are any string (trimmed for spaces) that follow the public key on each line of the admin and allowlist key files. Each comment is associated with the key it appears with. This commit does not attempt to parse the comments or use them in any way; it simply stores them for use in future commits.

The second commit implements an injectable checkName() function on the Room object, which is then used during Join() and Rename() operations to reject attempts by users to make use of a nick that is associated with another key. A nick is parsed from the first word of a key comment, and sanitized.

That's it. Things that are not in the implementation thus far:

  • Reserved nicks are //not// assigned automatically to connecting users; this should significantly reduce user surprise that would accompany such behaviour.
    • If a user attempts to connect with a nick reserved by another key, they are simply assigned a "guestX" nick, in the same way they would if they previously attempted to connect with a nick that was empty or already in use.
    • If a user attempts to use /nick or /rename to set a nick for a user reserved to a different key, the attempt fails with an error. The current implementation cannot distinguish between renaming attempts made by users from those made by admins, so all attempts are rejected.
  • command line options. The nick guard is currently enabled by default. Since user surprise is reduced significantly by the fact that nicks are not assigned automatically upon connection, a command line option to enable the behaviour is less necessary. If a command line option is desired to enable this behavior, it is easy to add.
  • symbol-setting. This, too, is easy to add in the future if desired. The essentials of the nick reservation feature can be considered in isolation from the aesthetics of how to present it to users.

I also added go fmt and test adjustments to this pr.

@Natorator
Copy link
Author

Natorator commented Feb 12, 2023

Here's something I noticed yesterday, that is worth discussing: how should we handle a situation where the same nick is reserved by multiple keys?

When a single fingerprint is reserving multiple nicks, the solution seems pretty obvious: the owner of the key can use any nick associated with it. The inverse case has a legible use case, but could also result from inadvertent mistakes made while configuring the admin and allowlist files.

First, the understandable use case: a user connects from different accounts on different hosts with different keys, at different times, but wants to use the same nick no matter where they connect from. So they register the same nick with both keys. In this case, the presumption is that either key would be able to use the nick (barring the case where it's already in use).

There's also the error case: A user is invited to select a nick to register, and the admin registers one that is already registered, without checking to see if it is already registered in the admin or allowlist files. Obviously allowing both users to use the nick would contradict user expectations of what the feature is intended to do. So what do we do in this case?

The current implementation allows NEITHER user to use the nick. I didn't plan it that way, but I'm happy with that at the moment. It will inspire users, and admins alike to discover the problem closer the the even that caused it - the extra reservation. It also averts a potential security issue (impersonating another user) at the expense of serving a legitimate use case, which I think is an appropriate "fail-closed" reaction to an unconsidered edge case. And, if you want to change the permissiveness of a feature, it's better for backwards compatibility to loosen constraints than tighten them.

There's another way to handle it, though: We could simply allow the //first// user in the files to use the nick, and prohibit subsequent reservations. Reservations in the admins file would be considered before reservations in the allowlist file, for this case.

Thoughts?

Copy link
Owner

@shazow shazow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't have much time to work on ssh-chat lately, it may take me a while to review new PRs.

Some high level thoughts:

  • I understand your desire to do a half-way feature and get it merged, but I'm reluctant to merge things that are not going to be forward-compatible, especially if they break backward compatibility in the exported interface of the code and we'll likely need to break it again in the future to fix shortcomings.
  • To that end, let's try to avoid changing exported interfaces when possible? That makes the PR easier to merge, because that means things can change "behind the scenes" without breaking too many things depending on it looking a specific way (also other serves maintain ssh-chat forks and it'll be annoying for them to change things back and forth).

I appreciate the direction you're heading in, and I think we can use what you've made if you want to leave it until I have time to work on ssh-chat more and I'll build more changes on top of it. :)

Or if you want to continue iterating, that's cool too.


Members *set.Set
}

// NewRoom creates a new room.
func NewRoom() *Room {
func NewRoom( checkName func( *message.User ) error ) *Room {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid changing public function signatures when possible (for backwards compatibility and also minimizing interface concerns). In this case, we can avoid passing in checkName as an argument by making it a new public element on the Room struct that can be set after NewRoom() is called.

@@ -200,11 +206,15 @@ func (r *Room) Leave(u *message.User) error {
}

// Rename member with a new identity. This will not call rename on the member.
func (r *Room) Rename(oldID string, u message.Identifier) error {
func (r *Room) Rename(oldID string, u *Member) error {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid changing this public function signature? I believe the host has everything it needs to look up users by ID?

Copy link
Author

@Natorator Natorator Feb 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Identifier interface technically doesn't fulfill the needs of checkName(). But... this could probably be fixed best with a signature like func ( nick string, fingerprint string) error instead, so that it's up to the caller to handle the lookups and type assertions. I'll try that.

@@ -58,7 +58,30 @@ type Host struct {

// NewHost creates a Host on top of an existing listener.
func NewHost(listener *sshd.SSHListener, auth *Auth) *Host {
room := chat.NewRoom()

checkName := func(user *message.User) error {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to pull this out into a helper rather than inline it, just to keep the NewHost function easier to follow.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same; I've got an implementation of this in progress (unpushed). Look for it soon.

@shazow
Copy link
Owner

shazow commented Feb 12, 2023

Here's something I noticed yesterday, that is worth discussing: how should we handle a situation where the same nick is reserved by multiple keys? [...]

I agree with your analysis. :)

Any of those options are fine for now, that's something we can change/improve in the future.

@@ -158,7 +162,8 @@ func (a *Auth) CheckPassphrase(passphrase string) error {
}

// Op sets a public key as a known operator.
func (a *Auth) Op(key ssh.PublicKey, d time.Duration) {
// comment is the string that follows the public key in openssh authorized_keys format
func (a *Auth) Op(key ssh.PublicKey, comment string, d time.Duration) {
Copy link
Owner

@shazow shazow Feb 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid changing Op(...) and Allow(...) by introducing a new Reserve(key ssh.PublicKey, id string, symbol rune) function?

[Edit: Expanded on the signature]

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to keep this a half-way solution, could maintain the same internal storage as a reconstructed "comment" for now, but I would appreciate if can at least converge on a "final" public code interface. :)

@Natorator
Copy link
Author

No worries about pacing; I think I'm going to set this down for a bit, too. You've taught me several good things about Go, maintaining OSS projects, and compatibility. Thank you for that!

@Natorator Natorator closed this Feb 12, 2023
@shazow
Copy link
Owner

shazow commented Feb 13, 2023

Alright. :) Let me know if you want to resume this another time.

I appreciate the effort you put into this!

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

Successfully merging this pull request may close these issues.

2 participants