From c514c2c121c0655eb137fe099dc2430ca405949a Mon Sep 17 00:00:00 2001 From: jbjohnso Date: Thu, 24 Apr 2014 13:00:07 -0400 Subject: [PATCH] Implement offload of compute-intensive authentication. With this change, an instance under pressure from new or bad authentication attempts will continue to be viable to authenticated sessions and clients with tokens (e.g. any http client that honors cookies). --- TODO | 21 +-------------------- confluent/auth.py | 37 ++++++++++++++++++++++++++++++++++--- confluent/main.py | 2 ++ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/TODO b/TODO index d8c50881..bf0ab2a6 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,3 @@ --user can bog down all requests by hammering it with bad auth requests causing - the auth facility to get bogged down in PBKDF - Option: - -a multiprocessing pool to handle new authentications. The auth action - itself would be stalled out whilst an attack was underway, but once - in, the respective session layers will provide a caching session that - should accelerate things after the client gets in once - -penalizing a client clearly trying to break in -other auth -pam if user exists but has no passphrase -keystone? @@ -14,13 +6,6 @@ -When a user account is changed, have httpapi and sockapi notified of changes to kill off related sessions. password changes are given a pass, but user deletion will result in immediate session termination --need event notification for config change- e.g. set attribute triggers consol - session object check to see if credentials changed - design is that calling code on init registers a callback for related node to - the in-context config manager object. callback shall be OO so no context - object parametr passed in. ipmi will use it for user/password/hardwaremanagement.manager - changes to console and command objects. console will use it to hook add/deletion - of nodes and/or indicating a different console.method or hardwaremanagement.method -When the time comes to dole out configuration/discovery, take page from xCAT 'flexdiscover' command, if possible bring an ipmi device under management by way of ipv6 to eliminate requirement for ip to be specified. @@ -40,8 +25,4 @@ Traceback (most recent call last): KeyError: '' --have pyghmi and friends do multiprocessing pools (particularly the PBKDF stuff in auth) --have console sessions be instructed as to more specific clue for unconnected - -misconfigured - console.method probably not set right - -unreachable - hardwaremanagement.manager probably wrong - -authentication failure - user/passphrase probable not right +-have pyghmi and friends do multiprocessing pools \ No newline at end of file diff --git a/confluent/auth.py b/confluent/auth.py index f60a2fc6..2276dfac 100644 --- a/confluent/auth.py +++ b/confluent/auth.py @@ -21,14 +21,18 @@ import confluent.config.configmanager as configmanager import eventlet +import eventlet.tpool import Crypto.Protocol.KDF as KDF import hashlib import hmac +import multiprocessing import time _passcache = {} _passchecking = {} +authworkers = None + def _prune_passcache(): # This function makes sure we don't remember a passphrase in memory more @@ -143,9 +147,13 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False): # throw it at the worker pool when implemented # maybe a distinct worker pool, wondering about starving out non-auth stuff salt, crypt = ucfg['cryptpass'] - crypted = KDF.PBKDF2(passphrase, salt, 32, 10000, - lambda p, s: hmac.new(p, s, hashlib.sha256).digest() - ) + # execute inside tpool to get greenthreads to give it a special thread + # world + #TODO(jbjohnso): util function to generically offload a call + #such a beast could be passed into pyghmi as a way for pyghmi to + #magically get offload of the crypto functions without having + #to explicitly get into the eventlet tpool game + crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt) del _passchecking[(user, tenant)] eventlet.sleep(0.05) # either way, we want to stall so that client can't # determine failure because there is a delay, valid response will @@ -154,3 +162,26 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False): _passcache[(user, tenant)] = passphrase return authorize(user, element, tenant) return None + + +def _apply_pbkdf(passphrase, salt): + return KDF.PBKDF2(passphrase, salt, 32, 10000, + lambda p, s: hmac.new(p, s, hashlib.sha256).digest()) + + +def _do_pbkdf(passphrase, salt): + # we must get it over to the authworkers pool or else get blocked in + # compute. However, we do want to wait for result, so we have + # one of the exceedingly rare sort of circumstances where 'apply' + # actually makes sense + return authworkers.apply(_apply_pbkdf, [passphrase, salt]) + + +def init_auth(): + # have a some auth workers available. Keep them distinct from + # the general populace of workers to avoid unauthorized users + # starving out productive work + global authworkers + # for now we'll just have one auth worker and see if there is any + # demand for more. I personally doubt it. + authworkers = multiprocessing.Pool(processes=1) \ No newline at end of file diff --git a/confluent/main.py b/confluent/main.py index adf0e6d4..bb00aff3 100644 --- a/confluent/main.py +++ b/confluent/main.py @@ -25,6 +25,7 @@ # It also will optionally snoop SLP DA requests import atexit +import confluent.auth as auth import confluent.consoleserver as consoleserver import confluent.pluginapi as pluginapi import confluent.httpapi as httpapi @@ -111,6 +112,7 @@ def run(): pluginapi.load_plugins() _daemonize() _updatepidfile() + auth.init_auth() signal.signal(signal.SIGINT, terminate) signal.signal(signal.SIGTERM, terminate) #TODO(jbjohnso): eventlet has a bug about unix domain sockets, this code