Page MenuHomePhabricator

Support 2fa in keystone/horizon
Closed, ResolvedPublic

Description

It would be good to tighten up horizon logins. With modern keystone this shouldn't be especially hard. Example:
https://ask.openstack.org/en/question/53047/how-to-implement-2-factor-authentication-in-horizon/

Keystone

kilo

Apply

In /etc/keystone.conf

  • Set password = keystone.auth.plugins.passwordoath.PasswordOATH in the [auth] section
  • Add an [oath] section with mysql details,
[oath]
dbuser = wiki_user
dbpass = s3kr3t
dbname = labswiki
dbhost = localhost

liberty

Apply

In /etc/keystone.conf

  • Add an [oath] section with mysql details,
[oath]
dbuser = wiki_user
dbpass = s3kr3t
dbname = labswiki
dbhost = localhost

Testing

If you're just setting this up for testing (i.e. in devstack), you can add enough mediawiki/OATHAuth database configuration to get it working with,

create database labswiki; use labswiki;
CREATE TABLE `oathauth_users` (   `id` int(11) NOT NULL,   `secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user` (   `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,   `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '',   PRIMARY KEY (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=235 DEFAULT CHARSET=utf8;
insert into user values (1,'admin'),(2,'user');
insert into oathauth_users values (1,'7XNRULECJPMYBOM4'),(2,'BL3R67WWTZ33OAJ2');
grant all on labswiki.* to 'wiki_user'@'localhost' identified by 's3kr3ts';
flush privileges;

If you're using devstack, you'll probably want to add to your local.conf,

ENABLED_SERVICES=key,n-api,n-crt,n-obj,n-cpu,n-net,n-cond,cinder,c-sch,c-api,c-vol,n-sch,n-novnc,n-xvnc,n-cauth,horizon,mysql,rabbit,ldap
KEYSTONE_IDENTITY_BACKEND=ldap

Event Timeline

Andrew raised the priority of this task from to Needs Triage.
Andrew updated the task description. (Show Details)
Andrew changed the visibility from "Public (No Login Required)" to "Custom Policy".
Andrew changed the edit policy from "All Users" to "Custom Policy".
Andrew changed Security from None to Software security bug.
Andrew subscribed.

@Andrew, is there a reason to keep this as a private security bug?

I'm a fan for adding 2FA in general :)

It lets you bypass wikitech 2FA

@Krenair, so currently a user can make direct calls into horizon without knowing the 2FA secret, even if the user has 2FA setup on their wikitech account?

@csteipp, yes. You can see for yourself by logging in to horizon. Horizon has specific policy controls which I've limited in some ways so that users can't be super-destructive.

Note that currently keystone acts only as a consumer of ldap identity -- accounts still have to be created and manipulated on wikitech.

Right now wikitech auth is a bit weird:

  • Primary authentication is against an ldap user account and password, via the LdapAuthentication extension
  • Second-factor auth is against a key stored in the mediawiki database, via the OATHAuth extension

Keystone auth happens only via ldap, using the same password as wikitech primary authentication.

Tight integration between wikitech/ldap:

  • make keystone a consumer of wikitech's auth (via oauth or the like)

Partial integration:

  • Move the second factor from mediawiki db into ldap
  • Write a keystone plugin that verifies the second factor
  • Make a new horizon login panel
  • Users will still create/manage accounts and 2fa via wikitech, but can use the same auth on Horizon/keystone

Total separation:

  • Move the second factor from mediawiki db into ldap
  • Write a keystone plugin that verifies the second factor
  • Write a full-blown horizon dashboard that fully manages the second factor
  • (Maybe) provide a 'new account' feature on Horizon

It looks like keystone can be adapted to use OATH (google 2fa), but it's custom code anyway. So having that fetch the token out of the DB doesn't seem like it would be too bad. It looks like you might be able to forward the entire authentication to mediawiki too, if you're already building a custom authn backend. But either way.

It doens't look like there's a good, standard way to put OATH secrets in ldap, and updating Ex:OATH would be a pain, pre AuthManger.

If there is a standard 2fa / OATH plugin for Keystone (I didn't see one in about 5 mins of googling... but might have missed it), we might be able to replace wikitech's auth plugin to use Keystone entirely. But otherwise, I think building 2fa in Keystone that checks for the secret in MediaWiki is probably the best path forward.

My $.02.

Ah, yeah, I was wondering about how to keep the secret in ldap.

As far as I know there is no official 2fa implementation in keystone, only a plugin architecture. @csteipp or @dpatrick, would one of you be willing/able to write a bit of python to validate an oath attempt against the wikitech database? I'm happy to write the user interface and can provide a hook that integrates with keystone, but I'm not especially confident of my ability to implement the validation without leaking secrets all over the place :)

(+Tyler, in case he's interested)

I did see some OATH code for keystone floating around on github, so it should just be a matter of grabbing the token from the wikitechwiki DB. I'll try to take a stab at some code later this week if no one else does it first.

@Andrew, finally getting a little time to work on this. It looks like identity changed a lot between v2 and v3, which version are we running?

Wikitech &c are using api v2, but v3 is installed and running as well. Probably best if you extend the v3 api, I'll see about switching horizon over to 3 when the time comes.

I'm able to generate and check tokens in python from the DB. Code here for reference using pypi's oath library. The actual functions the code users here can be easily extracted from that library if including it is a problem on whatever host this will run on.

So next is plugging into Keystone's auth extension framework. Is there an easy way to setup a dev instance in labs?

import oath
import base64
import pymysql.cursors


def verifyToken( secret, token ):
	( p, d ) = oath.accept_totp( secret, token )
	return p

def wikiToPythonSecret( secret ):
	try:
		return base64.b16encode( base64.b32decode( secret ) )
	except TypeError as e:
		#probably log this and fail gracefully?
		raise e

def getWikiSecret( userid ):
	connection = pymysql.connect( 'localhost', 'wiki_user', 'password', 'wikitechwiki' )
	cursor = connection.cursor()
	sql = "SELECT `secret` from `oathauth_users` WHERE `id`=%s LIMIT 1"
	cursor.execute( sql, userid )
	secret = cursor.fetchone()[0]
	connection.close()
	return secret


## Test
secret = getWikiSecret( 204 )
token = oath.totp( wikiToPythonSecret( secret ) );
if ( verifyToken( wikiToPythonSecret( secret ), token ) ):
	print "Token %s passes\n" % token
else:
	print "Token %s fails\n" % token

Note that wikitech's DB name is actually labswiki

So next is plugging into Keystone's auth extension framework. Is there an easy way to setup a dev instance in labs?

I don't think there's an existing test/dev one... if you need to set one up it should probably go in the openstack project or something

Basic keystone patch, so that logging into keystone respects 2fa.

@Andrew, assuming this part works, Horizon will probably need some way to ask for the token and pass it through to Keystone, right? Or is that already handled somewhere?

That plugin is simple enough that I probably won't bother with packaging... I'll just have puppet drop it right into the directory that needs it.

@csteipp, do you have any leads about how to extend the login panel in the Horizon UI?

@csteipp, do you have any leads about how to extend the login panel in the Horizon UI?

I took a bit of a look at in on Friday, but wasn't able to get a good enough understanding of how horizon is put together to figure out a good solution for adding the form field.

It looks like they're working on adding otp's upstream, but no Horizon patch yet that I can find (https://review.openstack.org/#/q/totp). Just Keystone and the cli client so far. If that all makes it in (and it looks like Keystone has one patch merged), it should make our work much easier. Just a matter of changing the OTP backend from native to asking mediawiki for it.

Small update, I've managed to get this somewhat working on Liberty. In my testing, Liberty has something weird where if you query it rapidly, it looses all of its configuration and reverts to the defaults. So while the previous patch for kilo generally works, I was able to get around the 2FA by just repeatedly calling the api.

I've patched password.py directly in this patch:

.

@csteipp, this is in place on production horizon and keystone. Please run whatever tests seem appropriate and let me know what you think.

So far in my testing, everything looks good.

Are you rolling this out further in a way that we can roll it back if we find issues later?

If we find issues with security then I can just switch Horizon back to read-only, with something like https://gerrit.wikimedia.org/r/#/c/270774/

<Luke081515> there is a field "totp token" which I have to fill in, but I don't know with which value

I don't think this user has 2-factor auth enabled (though haven't checked), but it apparently requires them to put a token in anyway?

Horizon will /only/ be available to users with 2fa enabled. I need to figure out a way to make that clearer in the gui.

Andrew claimed this task.
bd808 changed the visibility from "Custom Policy" to "Public (No Login Required)".Apr 21 2017, 8:37 PM