Пример #1
0
def get_service(id):
    '''
    Get service metadata and all credentials for this service. This endpoint
    allows basic authentication.
    '''
    if authnz.user_in_role('service') and not authnz.user_is_service(id):
        log.warning('Authz failed for service {0}.'.format(id))
        msg = 'Authenticated user is not authorized.'
        return jsonify({'error': msg}), 401
    log.debug('Authz succeeded for service {0}.'.format(id))
    try:
        service = Service.get(id)
    except Service.DoesNotExist:
        return jsonify({}), 404
    if (service.data_type != 'service' and
            service.data_type != 'archive-service'):
        return jsonify({}), 404
    try:
        credentials = _get_credentials(service.credentials)
    except KeyError:
        return jsonify({'error': 'Decryption error.'}), 500
    return jsonify({
        'id': service.id,
        'credentials': credentials,
        'enabled': service.enabled,
        'revision': service.revision,
        'modified_date': service.modified_date,
        'modified_by': service.modified_by
    })
Пример #2
0
def create_datakey(encryption_context):
    '''
    Create a datakey from KMS.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        log.warning('Creating a mock data key in keymanager.create_datakey.'
                    ' If you are not running in a development or test'
                    ' environment, this should not be happening!')
        return _create_mock_datakey()
    # Fernet key; from spec and cryptography implementation, but using
    # random from KMS, rather than os.urandom:
    #   https://github.com/fernet/spec/blob/master/Spec.md#key-format
    #   https://cryptography.io/en/latest/_modules/cryptography/fernet/#Fernet.generate_key
    key = base64.urlsafe_b64encode(
        kms.generate_random(NumberOfBytes=32)['Plaintext']
    )
    key_alias = app.config.get('KMS_MASTER_KEY')
    response = kms.encrypt(
        KeyId='alias/{0}'.format(key_alias),
        Plaintext=key,
        EncryptionContext=encryption_context

    )
    return {'ciphertext': response['CiphertextBlob'],
            'plaintext': key}
Пример #3
0
def send_event(services, msg):
    try:
        graphite_url = app.config.get('GRAPHITE_EVENT_URL')
        if not graphite_url:
            return
        username = app.config.get('GRAPHITE_USERNAME')
        password = app.config.get('GRAPHITE_PASSWORD')
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        prefixed_services = ['confidant-{0}'.format(service)
                             for service in services]
        event = {
            'what': 'confidant',
            'tags': ','.join(['confidant'] + prefixed_services),
            'data': msg
        }
        response = requests.post(
            graphite_url,
            auth=(username, password),
            headers=headers,
            data=json.dumps(event),
            timeout=1
        )
        if response.status_code != 200:
            msg = 'Post to graphite returned non-2000 status ({0}).'
            log.warning(msg.format(response.status_code))
    except Exception as e:
        log.warning('Failed to post graphite event. {0}'.format(e))
Пример #4
0
def get_service(id):
    '''
    Get service metadata and all credentials for this service. This endpoint
    allows basic authentication.
    '''
    if authnz.user_in_role('service') and not authnz.user_is_service(id):
        log.warning('Authz failed for service {0}.'.format(id))
        msg = 'Authenticated user is not authorized.'
        return jsonify({'error': msg}), 401
    log.debug('Authz succeeded for service {0}.'.format(id))
    try:
        service = Service.get(id)
    except Service.DoesNotExist:
        return jsonify({}), 404
    if (service.data_type != 'service'
            and service.data_type != 'archive-service'):
        return jsonify({}), 404
    try:
        credentials = _get_credentials(service.credentials)
    except KeyError:
        return jsonify({'error': 'Decryption error.'}), 500
    return jsonify({
        'id': service.id,
        'credentials': credentials,
        'enabled': service.enabled,
        'revision': service.revision,
        'modified_date': service.modified_date,
        'modified_by': service.modified_by
    })
Пример #5
0
def custom_modules(path):
    if not app.config['CUSTOM_FRONTEND_DIRECTORY']:
        return '', 200
    try:
        return send_from_directory(
            os.path.join(app.config['CUSTOM_FRONTEND_DIRECTORY'], 'modules'),
            path)
    except NotFound:
        log.warning('Client requested missing custom module {0}.'.format(path))
        return '', 200
Пример #6
0
def decrypt_token(token, _from):
    '''
    Decrypt a token.
    '''
    try:
        token_key = '{0}{1}'.format(hashlib.sha256(token).hexdigest(), _from)
    except Exception:
        raise TokenDecryptionError('Authentication error.')
    if token_key not in TOKENS:
        try:
            token = base64.b64decode(token)
            with stats.timer('kms_decrypt_token'):
                data = kms.decrypt(
                    CiphertextBlob=token,
                    EncryptionContext={
                        # This key is sent to us.
                        'to': app.config['AUTH_CONTEXT'],
                        # From a service.
                        'from': _from
                    })
            # Decrypt doesn't take KeyId as an argument. We need to verify the
            # correct key was used to do the decryption.
            # Annoyingly, the KeyId from the data is actually an arn.
            key_arn = data['KeyId']
            if key_arn != get_key_arn(app.config['AUTH_KEY']):
                raise TokenDecryptionError('Authentication error.')
            plaintext = data['Plaintext']
            payload = json.loads(plaintext)
        # We don't care what exception is thrown. For paranoia's sake, fail
        # here.
        except Exception:
            log.exception('Failed to validate token.')
            raise TokenDecryptionError('Authentication error.')
    else:
        payload = TOKENS[token_key]
    time_format = "%Y%m%dT%H%M%SZ"
    now = datetime.datetime.utcnow()
    try:
        not_before = datetime.datetime.strptime(payload['not_before'],
                                                time_format)
        not_after = datetime.datetime.strptime(payload['not_after'],
                                               time_format)
    except Exception:
        log.exception(
            'Failed to get not_before and not_after from token payload.')
        raise TokenDecryptionError('Authentication error.')
    delta = (not_after - not_before).seconds / 60
    if delta > app.config['AUTH_TOKEN_MAX_LIFETIME']:
        log.warning('Token used which exceeds max token lifetime.')
        raise TokenDecryptionError('Authentication error.')
    if not (now >= not_before) and (now <= not_after):
        log.warning('Expired token used.')
        raise TokenDecryptionError('Authentication error.')
    TOKENS[token_key] = payload
    return payload
Пример #7
0
def custom_modules(path):
    if not app.config['CUSTOM_FRONTEND_DIRECTORY']:
        return '', 200
    try:
        return send_from_directory(
            os.path.join(app.config['CUSTOM_FRONTEND_DIRECTORY'], 'modules'),
            path
        )
    except NotFound:
        log.warning('Client requested missing custom module {0}.'.format(path))
        return '', 200
Пример #8
0
 def decrypt(self, enc):
     # Disabled encryption is dangerous, so we don't use falsiness here.
     if app.config['USE_ENCRYPTION'] is False:
         log.warning('Not using encryption in CipherManager.decrypt'
                     ' If you are not running in a development or test'
                     ' environment, this should not be happening!')
         return base64.b64decode(re.sub(r'^DANGER_NOT_ENCRYPTED_', '', enc))
     if self.version == 2:
         f = Fernet(self.key)
         return f.decrypt(enc.encode('utf-8'))
     else:
         raise CipherManagerError('Bad cipher version')
Пример #9
0
 def decrypt(self, enc):
     # Disabled encryption is dangerous, so we don't use falsiness here.
     if app.config['USE_ENCRYPTION'] is False:
         log.warning('Not using encryption in CipherManager.decrypt'
                     ' If you are not running in a development or test'
                     ' environment, this should not be happening!')
         return enc
     if self.version == 2:
         f = Fernet(self.key)
         return f.decrypt(enc.encode('utf-8'))
     else:
         raise CipherManagerError('Bad cipher version')
Пример #10
0
 def encrypt(self, raw):
     # Disabled encryption is dangerous, so we don't use falsiness here.
     if app.config['USE_ENCRYPTION'] is False:
         log.warning('Not using encryption in CipherManager.encrypt'
                     ' If you are not running in a development or test'
                     ' environment, this should not be happening!')
         return raw
     if self.version == 2:
         f = Fernet(self.key)
         return f.encrypt(raw.encode('utf-8'))
     else:
         raise CipherManagerError('Bad cipher version')
Пример #11
0
def decrypt_key(data_key, encryption_context=None):
    '''
    Decrypt a datakey.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        log.warning('Decypting a mock data key in keymanager.decrypt_key.'
                    ' If you are not running in a development or test'
                    ' environment, this should not be happening!')
        return _decrypt_mock_datakey(data_key)
    sha = hashlib.sha256(data_key).hexdigest()
    if sha not in DATAKEYS:
        plaintext = kms.decrypt(
            CiphertextBlob=data_key,
            EncryptionContext=encryption_context)['Plaintext']
        DATAKEYS[sha] = plaintext
    return DATAKEYS[sha]
Пример #12
0
def decrypt_key(data_key, encryption_context=None):
    '''
    Decrypt a datakey.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        log.warning('Decypting a mock data key in keymanager.decrypt_key.'
                    ' If you are not running in a development or test'
                    ' environment, this should not be happening!')
        return _decrypt_mock_datakey(data_key)
    sha = hashlib.sha256(data_key).hexdigest()
    if sha not in DATAKEYS:
        plaintext = kms.decrypt(
            CiphertextBlob=data_key,
            EncryptionContext=encryption_context
        )['Plaintext']
        DATAKEYS[sha] = plaintext
    return DATAKEYS[sha]
Пример #13
0
def create_datakey(encryption_context):
    '''
    Create a datakey from KMS.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        log.warning('Creating a mock data key in keymanager.create_datakey.'
                    ' If you are not running in a development or test'
                    ' environment, this should not be happening!')
        return _create_mock_datakey()
    # Fernet key; from spec and cryptography implementation, but using
    # random from KMS, rather than os.urandom:
    #   https://github.com/fernet/spec/blob/master/Spec.md#key-format
    #   https://cryptography.io/en/latest/_modules/cryptography/fernet/#Fernet.generate_key
    key = base64.urlsafe_b64encode(
        kms.generate_random(NumberOfBytes=32)['Plaintext'])
    key_alias = app.config.get('KMS_MASTER_KEY')
    response = kms.encrypt(KeyId='alias/{0}'.format(key_alias),
                           Plaintext=key,
                           EncryptionContext=encryption_context)
    return {'ciphertext': response['CiphertextBlob'], 'plaintext': key}
Пример #14
0
def decrypt_token(token, _from):
    '''
    Decrypt a token.
    '''
    try:
        token_key = '{0}{1}'.format(
            hashlib.sha256(token).hexdigest(),
            _from
        )
    except Exception:
        raise TokenDecryptionError('Authentication error.')
    if token_key not in TOKENS:
        try:
            token = base64.b64decode(token)
            with stats.timer('kms_decrypt_token'):
                data = kms.decrypt(
                    CiphertextBlob=token,
                    EncryptionContext={
                        # This key is sent to us.
                        'to': app.config['AUTH_CONTEXT'],
                        # From a service.
                        'from': _from
                    }
                )
            # Decrypt doesn't take KeyId as an argument. We need to verify the
            # correct key was used to do the decryption.
            # Annoyingly, the KeyId from the data is actually an arn.
            key_arn = data['KeyId']
            if key_arn != get_key_arn(app.config['AUTH_KEY']):
                raise TokenDecryptionError('Authentication error.')
            plaintext = data['Plaintext']
            payload = json.loads(plaintext)
        # We don't care what exception is thrown. For paranoia's sake, fail
        # here.
        except Exception:
            log.exception('Failed to validate token.')
            raise TokenDecryptionError('Authentication error.')
    else:
        payload = TOKENS[token_key]
    time_format = "%Y%m%dT%H%M%SZ"
    now = datetime.datetime.utcnow()
    try:
        not_before = datetime.datetime.strptime(
            payload['not_before'],
            time_format
        )
        not_after = datetime.datetime.strptime(
            payload['not_after'],
            time_format
        )
    except Exception:
        log.exception(
            'Failed to get not_before and not_after from token payload.'
        )
        raise TokenDecryptionError('Authentication error.')
    delta = (not_after - not_before).seconds / 60
    if delta > app.config['AUTH_TOKEN_MAX_LIFETIME']:
        log.warning('Token used which exceeds max token lifetime.')
        raise TokenDecryptionError('Authentication error.')
    if not (now >= not_before) and (now <= not_after):
        log.warning('Expired token used.')
        raise TokenDecryptionError('Authentication error.')
    TOKENS[token_key] = payload
    return payload
Пример #15
0
    def decorated(*args, **kwargs):
        if not app.config.get('USE_AUTH'):
            return f(*args, **kwargs)

        auth = request.authorization
        headers = request.headers
        using_basic_kms_auth = (auth and auth.get('username')
                                and auth.get('password') != '')
        using_kms_auth = ('X-Auth-Token' in headers
                          and 'X-Auth-From' in headers)

        # User suppplied basic auth info
        if using_basic_kms_auth or using_kms_auth:
            if using_basic_kms_auth:
                _from = auth['username']
                token = auth['password']
            else:
                _from = headers['X-Auth-From']
                token = headers['X-Auth-Token']
            try:
                with stats.timer('decrypt_token'):
                    payload = keymanager.decrypt_token(token, _from)
                log.debug('Auth request had the following payload:'
                          ' {0}'.format(payload))
                role = 'service'
                msg = 'Authenticated {0} with role {1} via kms auth'
                msg = msg.format(_from, role)
                log.debug(msg)
                if role_has_privilege(role, f.func_name):
                    g.auth_role = role
                    g.username = _from
                    return f(*args, **kwargs)
                else:
                    msg = '{0} is not authorized to access {1}.'
                    msg = msg.format(_from, f.func_name)
                    log.warning(msg)
                    return abort(403)
            except keymanager.TokenDecryptionError:
                msg = 'Access denied for {0}. Authentication Failed.'
                msg = msg.format(_from)
                log.warning(msg)
                return abort(403)
        # If not using kms auth, require google auth.
        else:
            role = 'user'
            if not role_has_privilege(role, f.func_name):
                return abort(403)
            if 'email' in session.get('google_oauth2', []):
                if (app.config['USERS_FILE']
                        and get_logged_in_user_email() not in users):
                    msg = 'User not authorized: {0}'
                    log.warning(msg.format(get_logged_in_user_email()))
                    return abort(403)
                else:
                    g.auth_role = role
                    return f(*args, **kwargs)
            response = make_response()
            if request.is_secure:
                secure_cookie = True
            else:
                secure_cookie = False
            result = _authomatic.login(
                WerkzeugAdapter(request, response),
                'google',
                session=session,
                session_saver=lambda: app.save_session(session, response),
                secure_cookie=secure_cookie)
            if result:
                if result.error:
                    msg = 'Google auth failed with error: {0}'
                    log.error(msg.format(result.error.message))
                    return abort(403)
                if result.user:
                    result.user.update()
                    user = result.user
                    email_suffix = app.config['GOOGLE_AUTH_EMAIL_SUFFIX']
                    if email_suffix and not user.email.endswith(email_suffix):
                        return abort(403)
                    session['google_oauth2'] = {}
                    session['google_oauth2']['email'] = user.email
                    session['google_oauth2']['first_name'] = user.first_name
                    session['google_oauth2']['last_name'] = user.last_name
                    g.auth_role = role
                    # TODO: find a way to save the angular args
                    # authomatic adds url params google auth has stripped the
                    # angular args anyway, so let's just redirect back to the
                    # index.
                    return redirect(url_for('index'))
            return response
        return abort(403)
Пример #16
0
    def decorated(*args, **kwargs):
        if not app.config.get("USE_AUTH"):
            return f(*args, **kwargs)

        auth = request.authorization
        headers = request.headers
        using_basic_kms_auth = auth and auth.get("username") and auth.get("password") != ""
        using_kms_auth = "X-Auth-Token" in headers and "X-Auth-From" in headers

        # User suppplied basic auth info
        if using_basic_kms_auth or using_kms_auth:
            if using_basic_kms_auth:
                _from = auth["username"]
                token = auth["password"]
            else:
                _from = headers["X-Auth-From"]
                token = headers["X-Auth-Token"]
            try:
                with stats.timer("decrypt_token"):
                    payload = keymanager.decrypt_token(token, _from)
                log.debug("Auth request had the following payload:" " {0}".format(payload))
                role = "service"
                msg = "Authenticated {0} with role {1} via kms auth"
                msg = msg.format(_from, role)
                log.debug(msg)
                if role_has_privilege(role, f.func_name):
                    g.auth_role = role
                    g.username = _from
                    return f(*args, **kwargs)
                else:
                    msg = "{0} is not authorized to access {1}."
                    msg = msg.format(_from, f.func_name)
                    log.warning(msg)
                    return abort(403)
            except keymanager.TokenDecryptionError:
                msg = "Access denied for {0}. Authentication Failed."
                msg = msg.format(_from)
                log.warning(msg)
                return abort(403)
        # If not using kms auth, require google auth.
        else:
            role = "user"
            if not role_has_privilege(role, f.func_name):
                return abort(403)
            if "email" in session.get("google_oauth2", []):
                if app.config["USERS_FILE"] and get_logged_in_user_email() not in users:
                    msg = "User not authorized: {0}"
                    log.warning(msg.format(get_logged_in_user_email()))
                    return abort(403)
                else:
                    g.auth_role = role
                    return f(*args, **kwargs)
            response = make_response()
            if request.is_secure:
                secure_cookie = True
            else:
                secure_cookie = False
            result = _authomatic.login(
                WerkzeugAdapter(request, response),
                "google",
                session=session,
                session_saver=lambda: app.save_session(session, response),
                secure_cookie=secure_cookie,
            )
            if result:
                if result.error:
                    msg = "Google auth failed with error: {0}"
                    log.error(msg.format(result.error.message))
                    return abort(403)
                if result.user:
                    result.user.update()
                    user = result.user
                    email_suffix = app.config["GOOGLE_AUTH_EMAIL_SUFFIX"]
                    if email_suffix and not user.email.endswith(email_suffix):
                        return abort(403)
                    session["google_oauth2"] = {}
                    session["google_oauth2"]["email"] = user.email
                    session["google_oauth2"]["first_name"] = user.first_name
                    session["google_oauth2"]["last_name"] = user.last_name
                    g.auth_role = role
                    # TODO: find a way to save the angular args
                    # authomatic adds url params google auth has stripped the
                    # angular args anyway, so let's just redirect back to the
                    # index.
                    return redirect(url_for("index"))
            return response
        return abort(403)