コード例 #1
0
def resync(environ, start_response):
    """
    Handle Re-sync requests
    """
    try:
        # Validate caller address
        if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']:
            logger.error('Operation not permitted from IP %(REMOTE_ADDR)s',
                         environ)
            raise YKSyncError(
                'OPERATION_NOT_ALLOWED',
                'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ)
        # Parse query and check values
        resync_params = parse_querystring(environ['QUERY_STRING'])
        synclib = Sync()
        output = synclib.resync_local(resync_params)
        status_code = 200
        logger.info('Re-sync request by %s for keys: %s',
                    environ['REMOTE_ADDR'], resync_params['yk'])
    except YKSyncError as err:
        output = str(err)
        status_code = 401
    except Exception as err:
        logger.exception('ERROR: %s', err)
        output = ''
        status_code = 500
    finally:
        start_response(HTTP_STATUS_CODES[status_code],
                       [('Content-Type', 'text/plain')])
        return [output.encode()]
コード例 #2
0
ファイル: wsgi.py プロジェクト: tehmaze-labs/yubistack
def sync(environ, start_response):
    """
    Handle Sync requests
    """
    local_params = None
    try:
        # Validate caller address
        logger.debug('Received request from %(REMOTE_ADDR)s', environ)
        if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']:
            logger.info('Operation not permitted from IP %(REMOTE_ADDR)s', environ)
            logger.debug('Remote IP %s is not in allowed sync pool: %s',
                         environ['REMOTE_ADDR'], settings['SYNC_POOL'])
            raise YKSyncError('ERROR Authorization failed for %(REMOTE_ADDR)s)' % environ)
        sync_params = parse_querystring(environ['QUERY_STRING'])
        logger.info('Received: %s', sync_params)
        synclib = Sync()
        local_params = synclib.sync_local(sync_params)
        output = 'OK'
    except YKSyncError as err:
        output = str(err)
    except Exception as err:
        logger.exception('ERROR: %s', err)
        output = 'BACKEND_ERROR'
    finally:
        return wsgi_response(output, start_response, apikey=''.encode(), extra=local_params)
コード例 #3
0
def verify(environ, start_response):
    """
    Handle OTP Validation
    """
    apikey = ''.encode()
    try:
        params = parse_querystring(environ['QUERY_STRING'])
        public_id = params.get('otp', '?' * 12)[:12]
        logger.debug('%s: PROCESSED QUERYSTRING: %s', public_id, params)
        validator = Validator()
        apikey = validator.get_client_apikey(params.get('id'))
        client_signature = params.pop('h')
        server_signature = sign(params, apikey)
        if client_signature != server_signature:
            logger.error('[%s] Client hmac=%s != Server hmac=%s', public_id,
                         client_signature, server_signature)
            raise YKValError('BAD_SIGNATURE')
        for old_key, new_key in PARAM_MAP.items():
            if old_key in params:
                params[new_key] = params[old_key]
                params.pop(old_key)
        extra = validator.verify(**params)
        output = 'OK'
        logger.info('[%s] OTP Verified', public_id)
    except YKValError as err:
        output = '%s' % err
    except Exception as err:
        logger.exception('%s: Backend error: %s', public_id, err)
        output = 'BACKEND_ERROR'
    finally:
        return wsgi_response(output, start_response, apikey=apikey, extra=None)
コード例 #4
0
ファイル: wsgi.py プロジェクト: oriordan/yubistack
def resync(environ, start_response):
    """
    Handle Re-sync requests
    """
    try:
        # Validate caller address
        if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']:
            logger.error('Operation not permitted from IP %(REMOTE_ADDR)s', environ)
            raise YKSyncError('OPERATION_NOT_ALLOWED',
                              'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ)
        # Parse query and check values
        resync_params = parse_querystring(environ['QUERY_STRING'])
        synclib = Sync()
        output = synclib.resync_local(resync_params)
        status_code = 200
        logger.info('Re-sync request by %s for keys: %s',
                    environ['REMOTE_ADDR'], resync_params['yk'])
    except YKSyncError as err:
        output = str(err)
        status_code = 401
    except Exception as err:
        logger.exception('ERROR: %s', err)
        output = ''
        status_code = 500
    finally:
        start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', 'text/plain')])
        return [output.encode()]
コード例 #5
0
def decrypt(environ, start_response):
    """
    Handle OTP decryptions
    """
    _format = 'text'
    try:
        params = parse_querystring(environ['QUERY_STRING'])
        if params.get('format') == 'json' or environ[
                'HTTP_ACCEPT'] == 'application/json':
            _format = 'json'
        logger.debug('PROCESSED QUERYSTRING: %s', params)
        decryptor = Decryptor()
        output = decryptor.decrypt(params.get('otp'))
        if _format != 'json':
            output = 'OK counter=%(counter)s low=%(low)s high=%(high)s use=%(use)s\n' % output
        status_code = 200
    except YKKSMError as err:
        logger.exception('Decryption error: %s', err.error_code)
        output = '%s' % str(err)
        status_code = 400
    except Exception as err:
        logger.exception('Backend error: %s', err)
        output = 'Backend failure\n'
        status_code = 500
    finally:
        content_type = 'application/json' if _format == 'json' else 'text/plain'
        if _format == 'json':
            if isinstance(output, str):
                output = {'error': output}
            output = json.dumps(output)
        elif status_code != 200:
            output = 'ERR %s\n' % output
        start_response(HTTP_STATUS_CODES[status_code],
                       [('Content-Type', content_type)])
        return [output.encode()]
コード例 #6
0
ファイル: wsgi.py プロジェクト: oriordan/yubistack
def sync(environ, start_response):
    """
    Handle Sync requests
    """
    local_params = None
    try:
        # Validate caller address
        if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']:
            logger.error('Operation not permitted from IP %(REMOTE_ADDR)s', environ)
            raise YKSyncError('OPERATION_NOT_ALLOWED',
                              'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ)
        sync_params = parse_querystring(environ['QUERY_STRING'])
        logger.info('[%s] Received sync request from %s (counter: %s, use: %s, nonce: %s)',
                    sync_params.get('yk_publicname'), environ['REMOTE_ADDR'],
                    sync_params.get('yk_counter'), sync_params.get('yk_use'),
                    sync_params.get('nonce'))
        synclib = Sync()
        local_params = synclib.sync_local(sync_params)
        output = 'OK'
        status_code = 200
    except YKSyncError as err:
        output = str(err)
        status_code = 401
    except Exception as err:
        logger.exception('ERROR: %s', err)
        output = 'BACKEND_ERROR'
        status_code = 500
    finally:
        return wsgi_response(output, start_response, apikey=''.encode(),
                             extra=local_params, status=status_code)
コード例 #7
0
ファイル: wsgi.py プロジェクト: oriordan/yubistack
def verify(environ, start_response):
    """
    Handle OTP Validation
    """
    apikey = ''.encode()
    try:
        params = parse_querystring(environ['QUERY_STRING'])
        public_id = params.get('otp', '?' * 12)[:12]
        logger.debug('%s: PROCESSED QUERYSTRING: %s', public_id, params)
        validator = Validator()
        apikey = validator.get_client_apikey(params.get('id'))
        client_signature = params.pop('h')
        server_signature = sign(params, apikey)
        if client_signature != server_signature:
            logger.error('[%s] Client hmac=%s != Server hmac=%s',
                         public_id, client_signature, server_signature)
            raise YKValError('BAD_SIGNATURE')
        for old_key, new_key in PARAM_MAP.items():
            if old_key in params:
                params[new_key] = params[old_key]
                params.pop(old_key)
        extra = validator.verify(**params)
        output = 'OK'
        logger.info('[%s] OTP Verified', public_id)
    except YKValError as err:
        output = '%s' % err
    except Exception as err:
        logger.exception('%s: Backend error: %s', public_id, err)
        output = 'BACKEND_ERROR'
    finally:
        return wsgi_response(output, start_response, apikey=apikey, extra=None)
コード例 #8
0
ファイル: wsgi.py プロジェクト: oriordan/yubistack
def decrypt(environ, start_response):
    """
    Handle OTP decryptions
    """
    _format = 'text'
    try:
        params = parse_querystring(environ['QUERY_STRING'])
        if params.get('format') == 'json' or environ['HTTP_ACCEPT'] == 'application/json':
            _format = 'json'
        logger.debug('PROCESSED QUERYSTRING: %s', params)
        decryptor = Decryptor()
        output = decryptor.decrypt(params.get('otp'))
        if _format != 'json':
            output = 'OK counter=%(counter)s low=%(low)s high=%(high)s use=%(use)s\n' % output
        status_code = 200
    except YKKSMError as err:
        logger.exception('Decryption error: %s', err.error_code)
        output = '%s' % str(err)
        status_code = 400
    except Exception as err:
        logger.exception('Backend error: %s', err)
        output = 'Backend failure\n'
        status_code = 500
    finally:
        content_type = 'application/json' if _format == 'json' else 'text/plain'
        if _format == 'json':
            if isinstance(output, str):
                output = {'error': output}
            output = json.dumps(output)
        elif status_code != 200:
            output = 'ERR %s\n' % output
        start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', content_type)])
        return [output.encode()]
コード例 #9
0
ファイル: wsgi.py プロジェクト: tehmaze-labs/yubistack
def decrypt(environ, start_response):
    """
    Handle OTP decryptions
    """
    try:
        params = parse_querystring(environ['QUERY_STRING'])
        logger.debug('PROCESSED QUERYSTRING: %s', params)
        decryptor = Decryptor()
        output = decryptor.decrypt(params.get('otp'))
        output = 'OK counter=%(counter)s low=%(low)s high=%(high)s use=%(use)s\n' % output
    except YKKSMError as err:
        logger.exception('Decryption error: %s', err)
        output = '%s\n' % err
    except Exception as err:
        logger.exception('Backend error: %s', err)
        output = 'ERR Backend failure\n'
    finally:
        start_response('200 OK', [('Content-Type', 'text/plain')])
        return [output.encode()]
コード例 #10
0
ファイル: wsgi.py プロジェクト: tehmaze-labs/yubistack
def resync(environ, start_response):
    """
    Handle Re-sync requests
    """
    try:
        # Validate caller address
        if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']:
            logger.info('Operation not permitted from IP %(REMOTE_ADDR)s', environ)
            raise YKSyncError('ERROR Authorization failed for %(REMOTE_ADDR)s)' % environ)
        # Parse query and check values
        resync_params = parse_querystring(environ['QUERY_STRING'])
        synclib = Sync()
        output = synclib.resync_local(resync_params)
    except YKSyncError as err:
        output = str(err)
    except Exception as err:
        logger.exception('ERROR: %s', err)
        output = ''
    finally:
        start_response('200 OK', [('Content-Type', 'text/plain')])
        return [output.encode()]
コード例 #11
0
ファイル: wsgi.py プロジェクト: tehmaze-labs/yubistack
def authenticate(environ, start_response):
    """
    Handle authentications
    """
    REQUIRED_PARAMS = ['username', 'password', 'otp']
    try:
        # Parse POST request
        try:
            request_body_size = int(environ.get('CONTENT_LENGTH', 0))
        except ValueError:
            request_body_size = 0
        request_body = environ['wsgi.input'].read(request_body_size)
        params = parse_querystring(request_body.decode())
        _params = params.copy()
        _params['password'] = '******'
        logger.debug('PROCESSED QUERYSTRING: %s', _params)
        for req_param in REQUIRED_PARAMS:
            if req_param not in params:
                raise YKAuthError("Missing parameter: '%s'" % req_param)
        client = Client()
        output = client.authenticate(params['username'], params['password'], params['otp'])
    except YKAuthError as err:
        logger.exception('Authentication error: %s', err)
        output = False
    except Exception as err:
        logger.exception('Backend error: %s', err)
        output = None
        raise
    finally:
        if output == True:
            resp = (200, 'true')
        elif output == False:
            resp = (400, 'false')
        else:
            resp = (500, 'false')
        start_response('%s OK' % resp[0], [('Content-Type', 'text/plain')])
        return [resp[1].encode()]
コード例 #12
0
ファイル: wsgi.py プロジェクト: tehmaze-labs/yubistack
def verify(environ, start_response):
    """
    Handle OTP Validation
    """
    apikey = ''.encode()
    try:
        params = parse_querystring(environ['QUERY_STRING'])
        logger.debug('PROCESSED QUERYSTRING: %s', params)
        verifyer = Verifyer()
        kwargs = params.copy()
        if 'id' in kwargs:
            kwargs.pop('id')
        if 'otp' in kwargs:
            kwargs.pop('otp')
        apikey = verifyer.get_client_apikey(params.get('id'))
        output = verifyer.verify(params.get('id'), params.get('otp'), **kwargs)
    except YKValError as err:
        logger.exception('Validation error: %s', err)
        output = '%s' % err
    except Exception as err:
        logger.exception('Backend error: %s', err)
        output = 'BACKEND_ERROR'
    finally:
        return wsgi_response(output, start_response, apikey=apikey, extra=None)
コード例 #13
0
def sync(environ, start_response):
    """
    Handle Sync requests
    """
    local_params = None
    try:
        # Validate caller address
        if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']:
            logger.error('Operation not permitted from IP %(REMOTE_ADDR)s',
                         environ)
            raise YKSyncError(
                'OPERATION_NOT_ALLOWED',
                'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ)
        sync_params = parse_querystring(environ['QUERY_STRING'])
        logger.info(
            '[%s] Received sync request from %s (counter: %s, use: %s, nonce: %s)',
            sync_params.get('yk_publicname'), environ['REMOTE_ADDR'],
            sync_params.get('yk_counter'), sync_params.get('yk_use'),
            sync_params.get('nonce'))
        synclib = Sync()
        local_params = synclib.sync_local(sync_params)
        output = 'OK'
        status_code = 200
    except YKSyncError as err:
        output = str(err)
        status_code = 401
    except Exception as err:
        logger.exception('ERROR: %s', err)
        output = 'BACKEND_ERROR'
        status_code = 500
    finally:
        return wsgi_response(output,
                             start_response,
                             apikey=''.encode(),
                             extra=local_params,
                             status=status_code)
コード例 #14
0
def authenticate(environ, start_response):
    """
    Handle authentications
    """
    start_time = time.time()

    if 'client' not in PERSISTENT_OBJECTS:
        PERSISTENT_OBJECTS['client'] = Client()
    client = PERSISTENT_OBJECTS['client']
    _format = 'json' if environ.get(
        'HTTP_ACCEPT') == 'application/json' else 'text'
    params = {}

    try:
        # Parse POST request
        try:
            request_body_size = int(environ.get('CONTENT_LENGTH', 0))
        except ValueError:
            request_body_size = 0
        request_body = environ['wsgi.input'].read(request_body_size)
        params = parse_querystring(request_body.decode())
        _params = params.copy()
        _params['password'] = '******' * 8
        logger.debug('PROCESSED QUERYSTRING: %s', _params)
        # Checking parameters
        for req_param in REQUIRED_AUTH_PARAMS:
            if req_param not in params:
                raise YKAuthError('MISSING_PARAMETER')
        client.authenticate(params['username'], params['password'],
                            params['otp'])
        status_code = 200
        output = {'status': 'OK', 'message': 'Successful authentication'}
    except (YKAuthError, YKValError, YKSyncError, YKKSMError) as err:
        status_code = 400
        output = {'status': err.error_code, 'message': str(err)}
    except Exception as err:
        status_code = 500
        output = {
            'status': 'BACKEND_ERROR',
            'message': 'Backend error: %s' % err
        }
        logger.exception('Backend error: %s', err)
    finally:
        content_type = 'application/json' if _format == 'json' else 'text/plain'
        start_response(HTTP_STATUS_CODES[status_code],
                       [('Content-Type', content_type)])
        output['username'] = params.get('username', '')
        output['token_id'] = params.get('otp', '')[:-TOKEN_LEN]
        output['latency'] = round(time.time() - start_time, 3)
        output['src_ip'] = environ.get('REMOTE_ADDR')
        response = json.dumps(output if _format == 'json' else (
            status_code == 200))
        if status_code == 200:
            logger_sev = logging.INFO
            syslog_sev = syslog.LOG_INFO
        elif status_code == 400:
            logger_sev = logging.WARNING
            syslog_sev = syslog.LOG_WARNING
        else:
            logger_sev = logging.ERROR
            syslog_sev = syslog.LOG_ERR
        if settings['SYSLOG_WSGI_AUTH']:
            syslog.syslog(
                syslog_sev,
                response if _format == 'json' else json.dumps(output))
        logger.log(logger_sev,
                   '[%(username)s][%(token_id)s] %(status)s: %(message)s',
                   output)
        return [response.encode()]
コード例 #15
0
ファイル: wsgi.py プロジェクト: oriordan/yubistack
def authenticate(environ, start_response):
    """
    Handle authentications
    """
    start_time = time.time()

    if 'client' not in PERSISTENT_OBJECTS:
        PERSISTENT_OBJECTS['client'] = Client()
    client = PERSISTENT_OBJECTS['client']
    _format = 'json' if environ.get('HTTP_ACCEPT') == 'application/json' else 'text'
    params = {}

    try:
        # Parse POST request
        try:
            request_body_size = int(environ.get('CONTENT_LENGTH', 0))
        except ValueError:
            request_body_size = 0
        request_body = environ['wsgi.input'].read(request_body_size)
        params = parse_querystring(request_body.decode())
        _params = params.copy()
        _params['password'] = '******' * 8
        logger.debug('PROCESSED QUERYSTRING: %s', _params)
        # Checking parameters
        for req_param in REQUIRED_AUTH_PARAMS:
            if req_param not in params:
                raise YKAuthError('MISSING_PARAMETER')
        client.authenticate(params['username'], params['password'], params['otp'])
        status_code = 200
        output = {'status': 'OK',
                  'message': 'Successful authentication'}
    except (YKAuthError, YKValError, YKSyncError, YKKSMError) as err:
        status_code = 400
        output = {'status': err.error_code,
                  'message': str(err)}
    except Exception as err:
        status_code = 500
        output = {'status': 'BACKEND_ERROR',
                  'message': 'Backend error: %s' % err}
        logger.exception('Backend error: %s', err)
    finally:
        content_type = 'application/json' if _format == 'json' else 'text/plain'
        start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', content_type)])
        output['username'] = params.get('username', '')
        output['token_id'] = params.get('otp', '')[:-TOKEN_LEN]
        output['latency'] = round(time.time() - start_time, 3)
        output['src_ip'] = environ.get('REMOTE_ADDR')
        response = json.dumps(output if _format == 'json' else (status_code == 200))
        if status_code == 200:
            logger_sev = logging.INFO
            syslog_sev = syslog.LOG_INFO
        elif status_code == 400:
            logger_sev = logging.WARNING
            syslog_sev = syslog.LOG_WARNING
        else:
            logger_sev = logging.ERROR
            syslog_sev = syslog.LOG_ERR
        if settings['SYSLOG_WSGI_AUTH']:
            syslog.syslog(syslog_sev, response if _format == 'json' else json.dumps(output))
        logger.log(logger_sev, '[%(username)s][%(token_id)s] %(status)s: %(message)s', output)
        return [response.encode()]
コード例 #16
0
ファイル: replicate.py プロジェクト: oriordan/yubistack
def main():
    """
    Main program
    """
    sync = Sync()
    queue_data = sync.db.read_queue()
    servers = set([x['server'] for x in queue_data])

    for server in servers:
        logger.debug('Processing queue for server %s', server)
        items = [x for x in queue_data if x['server'] == server]
        try:
            for item in items[:20]:
                url = '%s?otp=%s&modified=%s&%s' % (item['server'], item['otp'], item['modified'],
                                                    item['info'].split(',')[0])
                resp = requests.get(url)
                if resp.status_code == 200:
                    if resp.text.rstrip().endswith('status=OK'):
                        # Read remote counters
                        remote_params = parse_sync_response(resp.text)
                        # Update local counters
                        sync.db.update_db_counters(remote_params)
                        # Get OTP counters
                        otp_params = parse_querystring(item['info'].split(',')[0])
                        # Get validation counters before processing the OTP
                        validation_params = parse_querystring(item['info'].split(',')[1])
                        validation_params = {'yk_counter': validation_params['local_counter'],
                                             'yk_use': validation_params['local_use']}
                        # Get current local counters
                        local_params = sync.db.get_local_params(otp_params['yk_publicname'])

                        if counters_gt(validation_params, remote_params):
                            logger.info('[%s]: Remote server out of sync compared to counters '
                                        'at validation request time.', server)
                        if counters_gt(remote_params, validation_params):
                            if counters_eq(remote_params, otp_params):
                                logger.info('[%s]: Remote server had received the current '
                                            'counter values already.', server)
                            else:
                                logger.info('Local server out of sync compared to counters '
                                            'at validation request time.')
                        if counters_gt(local_params, remote_params):
                            logger.info('Local server out of sync compared to current local '
                                        'counters. Local server updated.')
                        if counters_gt(remote_params, otp_params):
                            logger.info('[%s]: Remote server has higher counters than OTP. '
                                        'This response would have marked the OTP as invalid.',
                                        server)
                        elif counters_eq(remote_params, otp_params) and \
                                remote_params['nonce'] != otp_params['nonce']:
                            logger.info('[%s]: Remote server has equal counters as OTP '
                                        'and nonce differs. This response would have '
                                        'marked the OTP as invalid.', server)
                        # Delete queue entry
                        sync.db.remove_from_queue(server, item['modified'], item['server_nonce'])
                    elif resp.text.rstrip().endswith('status=BAD_OTP'):
                        logger.warning('[%s]: Remote server says BAD_OTP, pointless to try '
                                       'again, removing from queue.', server)
                        # Delete queue entry
                        sync.db.remove_from_queue(server, item['modified'], item['server_nonce'])
                    else:
                        logger.error('[%s]: Remote server refused our sync request. '
                                     'Check remote server logs.', server)
                else:
                    logger.error('[%s]: Remote server refused our sync request. '
                                 'Check remote server logs.', server)
        except (requests.exceptions.Timeout,
                requests.exceptions.ConnectionError) as err:
            logger.error('Failed to connect to server %s: %s', server, err)
            continue