65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Vandium 简化和保护您的 Node.js AWS Lambda 代码 - 第一部分

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (1投票)

2016年4月11日

CPOL

5分钟阅读

viewsIcon

10479

三部分系列的第一部分,介绍如何使用开源项目 Vandium 简化和保护 AWS Lambda 代码。

引言

过去几年最令人兴奋的新云技术之一是 Amazon Web Services (AWS) Lambda 环境的出现。这是我们第一次能够为一个特定事件(例如上传、数据库或 API 调用)执行任意代码片段。这仅仅是我们正在看到的从服务器轮询模型向完全事件驱动的无服务器云架构过渡的新趋势的开始。这种趋势现在已经从处理存储、电子邮件、数据库事件一直延伸到构建功能齐全的 RESTful API,而无需管理服务器。

由于 Lambda 事件处理程序不需要管理服务器进程,因此编写它们与您可能习惯的略有不同。例如,Lambda 处理程序中不存在维护状态的概念。有加载处理程序和执行处理程序。仅此而已。唯一的缺点是,您需要为代码运行的时间付费,但许多人发现这种模式比运行服务器更便宜。

Vandium 是一个开源的 Node.js 模块,它可以使编写处理程序更加容易,同时为您的代码添加验证、健壮性和安全性。从这些好处来看,一个总体影响是您需要编写的代码量将大大减少。当您编写的代码更少时,意味着需要维护的行数就更少,从而降低了潜在的技术债务。

我们的 Lambda 处理程序

下面的代码是一个 Lambda 处理程序的示例,该处理程序将一条文本消息从一个系统用户发送到另一个用户。

'use strict';

// our token validation service
const tokenValidator = require( './lib/token-validator' );

// our nosql datastore
const db = require( './lib/db' );

// message service
const msgService = require( './lib/messages' );

exports.handler = function( event, context, callback ) {

    tokenValidator.validateToken( event.token, function( err, result ) {
    
        if( err ) {
      
            return callback( err );
        }
    
        // sender id
        let senderId = result.userId;

        db.userExists( event.userId, function( err, result ) {
      
            if( err ) {
        
                return callback( err );
            }
      
            msgService.send( senderId, event.userId, event.message, function( err, result ) {
        
                if( err ) {
          
                    return callback( err );
                }
        
                callback( null, 'sent' );
            });
        });
    });
};

Lambda 使用 callback 函数来捕获最终执行状态并将其返回给调用者。这个例子很简单,而且确实有效,但缺点是对输入参数没有进行验证。这可能导致昂贵的数据库和服务层调用,耗费过多的时间和资源。

验证时间

从安全角度来看,验证输入是关键步骤,正如 Open Web Application Security Project (OWASP) 所指出的那样。

最常见的 Web 应用程序安全弱点是未能正确验证来自客户端或环境的输入。这种弱点导致应用程序中几乎所有的主要漏洞,例如 解释器注入、区域设置/Unicode 攻击、文件系统攻击和缓冲区溢出。永远不应该信任来自客户端的数据,因为客户端有各种可能性来篡改数据。(来源:OWASP/Data_Validation

让我们使用以下规则来验证事件对象中的输入:

  • event.userId 必须是 UUID
  • 消息应已修剪,长度在 1 到 255 个字符之间。

我们更新后的 Lambda 处理程序现在看起来像这样:

'use strict';

// our token validation service
const tokenValidator = require( './lib/token-validator' );

// our nosql datastore
const db = require( './lib/db' );

// message service
const msgService = require( './lib/messages' );

// from wikipedia
const UUID_REGEX = /^[0–9a-fA-F]{8}-[0–9a-fA-F]{4}-[0–9a-fA-F]{4}-[0–9a-fA-F]{4}-[0–9a-fA-F]{12}$/;

exports.handler = function( event, context, callback ) {
    
    if( !event.userId || !UUID_REGEX.test( event.userId ) ) {
        
        return callback( new Error( 'invalid userId' ) );
    }
    
    if( !event.message ) {
        
        return callback( new Error( 'missing message' ) );
    }

    let message = event.message.toString().trim();
    
    if( message.length < 1 ) {
        
        return callback( new Error( 'message is too short' ) );
    }
    else if( message.length > 255 ) {
        
        return callback( new Error( 'message is too long' ) );
    }
    
    tokenValidator.validateToken( event.token, function( err, result ) {
    
        if( err ) {
      
            return callback( err );
        }
    
        // sender id
        let senderId = result.userId;

        db.userExists( event.userId, function( err, result ) {
      
            if( err ) {
        
                return callback( err );
            }
      
            msgService.send( senderId, event.userId, message, function( err, result ) {
        
                if( err ) {
          
                    return callback( err );
                }
        
                callback( null, 'sent' );
            });
        });
    });
};

您可能会注意到,当正确完成时,用于验证的代码可能比执行实际预期操作的代码要多。

应用 Vandium

Vandium 的目的是在无缝添加验证和安全等关键功能的同时,减少您在处理程序中需要编写的代码量。它通过定义一组验证规则,然后 包装现有的 处理程序来工作。

要将 vandium 安装到您的项目中,请在您项目的根目录中运行以下命令:

npm install vandium --save

以下代码示例是将 Vandium 应用于文本消息传递示例。

'use strict';

const vandium = require( 'vandium' );

// our token validation service
const tokenValidator = require( './lib/token-validator' );

// our nosql datastore
const db = require( './lib/db' );

// message service
const msgService = require( './lib/messages' );

vandium.validation( {
    
        userId: vandium.types.uuid().required(),
        
        message: vandium.types.string().min( 1 ).max( 255 ).required()
    });

exports.handler = vandium( function( event, context, callback ) {
    
    tokenValidator.validateToken( event.token, function( err, result ) {
    
        if( err ) {
      
            return callback( err );
        }
    
        // sender id
        let senderId = result.userId;

        db.userExists( event.userId, function( err, result ) {
      
            if( err ) {
        
                return callback( err );
            }
      
            msgService.send( senderId, event.userId, event.message, function( err, result ) {
        
                if( err ) {
          
                    return callback( err );
                }
        
                callback( null, 'sent' );
            });
        });
    });
});

使用 Vandium,我们的代码变得更小,并且更专注于发送消息的任务。事实上,处理程序代码与我们最初的代码(不带验证)完全相同。发生这种情况是因为在调用处理程序 之前,验证由 Vandium 负责。

Vandium 不仅仅是简化您的 Lambda 处理程序和减少您的代码。在 Vandium 的验证代码中,它将强制要求事件对象仅包含已指定的属性。这一点很重要,因为如果您的代码的功能在开发过程的后期发生变化,并且如果没有同步更新验证,Vandium 将在处理程序处失败。这对于在测试期间捕获问题而不是在生产环境中捕获问题非常有用。

此外,Vandium 可以做一些您可能忘记做的事情,或者一些您可能甚至没有考虑过做的事情,例如自动修剪输入 string。在编写生产级代码时,诸如输入验证和修剪 string 之类的任务非常繁琐,而且常常直到出现问题才会被注意到。Vandium 的验证系统旨在处理编写代码的繁琐方面。Vandium 验证功能的另一个例子是,当与 vandium.types.number() 类型一起使用时,它可以自动将 string 值转换为数字。布尔值和二进制数据也有类似的模式。

检测潜在攻击

除了清理您的 string 和转换类型之外,Vandium 还会采取额外步骤。Vandium 将分析事件对象中的所有 string,以确定调用者是否正在尝试执行 SQL 注入 (SQLi) 攻击。现在您可能会说:“我不用 SQL,所以我不会受到影响”,从操作的角度来看这是正确的,但从安全角度来看,识别攻击正在发生是一个重要的向量。Vandium 的默认安全设置将通过 console.log 进行日志记录来报告潜在攻击。

例如,如果潜在攻击者使用看起来像这样的事件调用处理程序:

{
    "userId": "b08f86af-35da-48f2-8fab-cef3904660bd",
    "message": "What's up?\'; drop table users;--"
}

Vandium 将检测到 message string 包含攻击的指纹,并在 console.log 中报告尝试了攻击。现在,在我们的例子中,我们不使用 SQL 数据库,所以从漏洞的角度来看我们没有担忧。

阻止攻击

您可能会问:“我是否想让潜在攻击者浪费我的资源?” 答案可能是否定的。为什么要浪费宝贵的资源,即使 Lambda 的收费非常低,在规模化执行服务时总是有成本的。由于 Vandium 会检测并记录潜在攻击,因此您可以在添加以下一行到验证部分之后,在调用处理程序之前停止执行:

vandium.protect.sql.fail();

这一行将在 Vandium 检测到潜在威胁(无论是否易受攻击)时记录并停止处理程序的执行。

第一部分总结(双关语!)

通过使用 Vandium,我们能够以非侵入性的方式快速添加输入验证,代码简单易用且易于维护。只需将 Vandium 应用于您现有的 Lambda 代码,Vandium 就能检测、记录和阻止潜在的 SQL 注入攻击。

未完待续,请看 第二部分

最初发布于 medium

© . All rights reserved.