rubygems / rubygems.org

The Ruby community's gem hosting service.
https://rubygems.org
MIT License
2.33k stars 923 forks source link

RFC: Add 2FA auth to RubyGems.org #1252

Closed jeroenvisser101 closed 5 years ago

jeroenvisser101 commented 8 years ago

Is this something you guys would like to get a PR for? I'd be willing to invest some time and see if I can get it to work with clearance?

dwradcliffe commented 8 years ago

We have talked about this a little bit in the past but never really had the time to think it through. Before we say yes or no I'd like to see a plan for what it would look like and how it would work.

nateberkopec commented 8 years ago

FWIW, as a user, I would prefer Google Authenticator, because I already have it installed.

jeroenvisser101 commented 8 years ago

@dwradcliffe – totally, I'll work on mockups and a specification of how it would work (flowchart maybe?)

@nateberkopec – absolutely, scannable QR codes will most certainly be part of this, since Authy, Google Authenticator but also 1Password use this and it has become the industry standard (or so I believe). Thanks for the input!

jeroenvisser101 commented 8 years ago

Hey, even though this is super cool, I don't think I have the time that I need to invest in this. I'll leave this open for anyone to pick up.

jvanbaarsen commented 7 years ago

@dwradcliffe I would love to work on this feature. You said something about wanting to see some things before implementation works get started. Can you elaborate what type of things you would like to see?

duckinator commented 6 years ago

Hi! I asked for some input on this, since it was brought up on Twitter again.

I don't have any expertise in this area, but I'm happy to help put a plan together for it.

Some thoughts from @indirect:

  1. First step for setting up 2FA should be verifying the email address.
  2. Don't use SMS, because it both costs money and isn't secure.
  3. 2FA setup via QR code or seed number.
  4. Backup codes to save in case of losing the token seed.
  5. After configuring it, requiring a valid token input to validate the user has it set up right before enabling it. (To avoid locking them out of their account.)

Miscellaneous notes from discussion with @indirect and @evanphx:

  1. 2FA gem push is very important, because most authenticated access to rubygems.org is publishing gems.
    • I personally suspect this may actually be more important than website 2FA.
  2. 2FA gem push would impact how Bundler's rake release works
    • However, 2FA would be opt-in, so this shouldn't be a blocking issue.
  3. A good first step is improving handling of the token generated by gem login.
    • E.g., using a JSON Web Token with an expiration, instead of the current one that lasts indefinitely.
  4. A potential approach to improving gem push would be building a new workflow around using SSH public keys.
    • @evanphx has experimented with this before, and may revisit it again. I've offered to help, if it's revisited.
  5. This would require a new RubyGems release that supports a 2FA challenge.
duckinator commented 6 years ago

notes from talking with npm folks

npm added 2FA recently, so I talked to @iarna about how npm approached it!

npm's general approach

  1. If the server wants 2FA for an action, it looks for the the npm-otp HTTP header.
    • If it doesn't find one or it's incorrect, it returns an HTTP 401 with a WWW-Authentication header containing OTP.
  2. If the client gets an HTTP 401: a. It checks for a WWW-Authenticate header and if it contains OTP. b. If it does, it either instructs the user run the command again with --otp=####, or prompts them for the OTP interactively. (Varies depending on which command.)
  3. Overall design:
    • Use a library on the server to produce a shared secret and recovery codes.
    • Server sends an otpauth URL, in the form of otpauth://?secret=<SECRET>, to the client.
    • The client produces a QR code by passing the otpauth URL to a library that handles that.
    • The user does a test authentication, to make sure it works.
    • If they succeed, the server sends the recovery codes.
  4. Two 2FA modes:
    • 2FA ONLY for login.
    • 2FA for login, publication and anything that changes package ownership or permissions.

Note: otpauth urls can have more stuff in them, and it's worth seeing if any of that would be useful.

standalone client

It was originally developed as a standalone client, then moved into npm later on.

  1. The client does multiple things, because they combined 2FA, command line profile editing, and custom authentication tokens all into one thing.
    • The reasoning for this was that the API for setting 2FA was implemented as a subset of the API for setting other profile values.
  2. Code for handling 401 responses: https://github.com/npm/npm-profile/blob/latest/lib/index.js#L223
  3. Code for CLI 2FA workflow: https://github.com/npm/npm-profile/blob/latest/cmds/tfa.js

Here's the general flow for how their library is used:

profile.set({tfa: {password, mode}})
profile.set({tfa: [otp]})

suggestions

  1. 2FA should also be required for adding/removing maintainers. E.g., the gem owner command.
  2. Have the user go through a test authentication flow before actually enabling 2FA, just to make sure it works.
    • npm originally considered having two confirmations, but decided against it while it was still in development.
indirect commented 6 years ago

I have used npm's fully command-line 2fa flow, and it was pretty good. I would be okay with ending up somewhere similar. I also like the idea of 2fa only for login or 2fa for login/push/owner actions.

ecnelises commented 6 years ago

Hello everyone, I'm planning to apply this as a Google Summer of Code project. For the workflow, I have an idea that the authentication should have three levels:

  1. auth-only, just like what npm does, for login and disabling 2FA
  2. auth-and-package, for actions about package, like gem push or gem owner --add, gem owner --remove
  3. auth-and-write, for anything that will change data about current user

Will this be too complicated? And npm's two-level also works fine.

Also, as @duckinator pointed out, change token mechanism to JWT is nice. Maybe I can start from here. But what will be the result of incompatibility with old client that does not support JWT?

Anyway, I'll look for a good solution for OTP generating and create a mock site for feature test.

connorshea commented 6 years ago

@ecnelises did this end up going anywhere? :)

devise-two-factor is used in GitLab, I’m not sure if that’s still the best solution but it seemed like it was when it was added ~two years ago. Also I’m not sure if RubyGems.org even uses devise in the first place.

https://github.com/tinfoil/devise-two-factor

Houdini might also be an option, but it’s not a super active project either: https://github.com/Houdini/two_factor_authentication

ecnelises commented 6 years ago

@connorshea Thanks for your notification.

But RubyGems.org doesn't have dependency with Devise. Instead, it uses Clearance, a simple library for rails application use authentication by email and password. (Occasion for RubyGems.org is not complex. It's much simpler than GitLab, using devise may be a burden)

I also checked the both library for 2FA, both depends on Devise and ROTP which is the real logic for OTP generation. To keep things simple, I think just use it is a good idea.

ethagnawl commented 6 years ago

Have you been able to make any progress on this, @ecnelises? After today's NPM fiasco, this (missing) feature immediately jumped to mind.

I've not contributed to this project before, but would be happy to help get this shipped.

Also, FWIW, I'd vote for Google Authenticator or Authy.

connorshea commented 6 years ago

If the 2FA feature uses the standard TOTP algorithm it should support both Google Authenticator and Authy out of the box as far as I understand.

connorshea commented 6 years ago

@ethagnawl also, see #1725, #1729, and #1753. Looks like this is well on its way :)

ecnelises commented 6 years ago

@ethagnawl Some of them are finished and deployed. If you are interested in it, just add a cookie using JavaScript console document.cookie='mfa_feature=true;path=/' when visiting rubygems.org. You can now turn it on for login, but please be sure to keep the recovery codes well :) We now hide it by cookie because it has not been totally completed.

@connorshea Thank you :)

ethagnawl commented 6 years ago

@connorshea @ecnelises Excellent! Thanks for the prompt replies and your efforts on this issue!

envygeeks commented 6 years ago

If the 2FA feature uses the standard TOTP algorithm it should support both Google Authenticator and Authy out of the box as far as I understand.

@connorshea it'll support most any authenticator. Google Auth, Authy, 1Password, LastPass, pretty much any other generator that implements it.

ioquatix commented 5 years ago

Sorry to jump in here, but I'm trying to use OTP and rake release now just hangs. What do I need to do? Can we improve the documentation to explain some of the basics? Is this a bug?

sonalkr132 commented 5 years ago

Thank you pointing out @ioquatix. We are going to track this issue here https://github.com/bundler/bundler/issues/6854

jeroenvisser101 commented 5 years ago

Since this is (at least partially) implemented, I'm going to close the RFC.

matschaffer commented 5 years ago

Incase anyone googles their way to @ethagnawl 's comment, you don't have to mess with cookies anymore.

The Rubygems UI provides a "Multifactor Authentication" section on https://rubygems.org/profile/edit

Screen Shot 2019-07-09 at 3 13 07 PM