保护 Node-RED

默认情况下,Node-RED 编辑器没有安全保护——任何可以访问其 IP 地址的人都可以访问编辑器并部署更改。

这仅适用于在受信任的网络上运行的情况。

本指南描述了如何保护 Node-RED。安全性分为三个部分:

启用 HTTPS 访问

要启用通过 HTTPS 访问 Node-RED 编辑器,而不是默认的 HTTP,您可以在您的 设置文件 中使用 https 配置选项。

https 选项可以是静态设置的一组配置选项,或者自 Node-RED 1.1.0 起,可以是一个返回选项的函数。

完整的选项集在 这里记录

至少,选项应包括:

  • key - 以 PEM 格式提供的私钥,作为 StringBuffer
  • cert - 以 PEM 格式提供的证书链,作为 StringBuffer
有关如何生成证书的指南,请参考 此指南

默认的 Node-RED 设置文件包含一个被注释掉的 https 部分,可以用来从本地文件加载证书。

https: {
    key: require("fs").readFileSync('privkey.pem'),
    cert: require("fs").readFileSync('cert.pem')
},

自 Node-RED 1.1.0 起

如果 https 属性是一个函数,它可以用来返回选项对象。该函数可以选择性地返回一个 Promise,该 Promise 将解析为选项对象,从而允许它异步完成。

https: function() {
    return new Promise((resolve, reject) => {
        var key, cert;
        // 做一些工作以获取有效证书
        // ...
        resolve({
            key: key,
            cert: cert
        });
    });
}

刷新 HTTPS 证书

自 Node-RED 1.1.0 起

可以配置 Node-RED 定期刷新其 HTTPS 证书,而无需重启 Node-RED。要做到这一点:

  1. 必须 使用 Node.js 11 或更高版本
  2. https 设置 必须 是一个可以调用的函数以获取更新的证书
  3. 设置 httpsRefreshInterval 为 Node-RED 调用 https 函数以获取更新详情的频率(以小时为单位)。

https 函数应确定当前证书是否将在下一个 httpsRefreshInterval 周期内过期,如果是,则生成一组新的证书。如果不需要更新,则函数可以返回 undefinednull

编辑器和管理 API 安全

编辑器和管理 API 支持两种类型的身份验证:

  • 基于用户名/密码凭证的身份验证
  • 针对任何 OAuth/OpenID 提供者(如 Twitter 或 GitHub)的身份验证

基于用户名/密码的身份验证

要启用编辑器和管理 API 上的用户身份验证,请在您的 设置文件 中取消注释 adminAuth 属性:

adminAuth: {
    type: "credentials",
    users: [
        {
            username: "admin",
            password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
            permissions: "*"
        },
        {
            username: "george",
            password: "$2b$08$wuAqPiKJlVN27eF5qJp.RuQYuy6ZYONW7a/UWYxDTtwKFCdB8F19y",
            permissions: "read"
        }
    ]
}

users 属性是一个用户对象的数组。这允许您定义多个用户,每个用户可以具有不同的权限。

上述示例配置定义了两个用户。一个名为 admin,有权在编辑器内执行所有操作,密码为 password。另一个名为 george,被赋予只读访问权限。

请注意,密码使用 bcrypt 算法进行了安全哈希处理。

注意 : 在之前的 Node-RED 版本中,设置 httpAdminAuth 可用于启用编辑器上的 HTTP 基本身份验证。此选项已被弃用,不应使用。

生成密码哈希

如果您使用的是 Node-RED 1.1.0 或更高版本,您可以使用命令:

node-red admin hash-pw

对于较早版本的 Node-RED,您可以选择:

  • 安装单独的 node-red-admin 命令行工具 并使用命令:

     node-red-admin hash-pw
    
  • 或者,找到 Node-RED 安装目录并使用命令:

     node -e "console.log(require('bcryptjs').hashSync(process.argv[1], 8));" your-password-here
    

在所有情况下,您将获得密码的哈希值,然后可以将其粘贴到您的设置文件中。

基于 OAuth/OpenID 的身份验证

要使用外部身份验证源,Node-RED 可以利用 Passport 提供的广泛策略。

Node-RED 身份验证模块适用于 TwitterGitHub。它们封装了一些特定于策略的细节,使其更易于使用。它们也可以用作通过其他相似策略进行身份验证的模板。

以下示例展示了如何在不使用我们提供的 auth 模块的情况下配置以向 Twitter 进行身份验证。

adminAuth: {
    type:"strategy",
    strategy: {
        name: "twitter",
        label: '使用 Twitter 登录',
        icon:"fa-twitter",
        strategy: require("passport-twitter").Strategy,
        options: {
            consumerKey: TWITTER_APP_CONSUMER_KEY,
            consumerSecret: TWITTER_APP_CONSUMER_SECRET,
            callbackURL: "http://example.com/auth/strategy/callback",
            verify: function(token, tokenSecret, profile, done) {
                done(null, profile);
            }
        },
    },
    users: [
       { username: "knolleary",permissions: ["*"]}
    ]
}

strategy 属性接受以下选项:

  • name - 使用的护照策略名称
  • strategy - 护照策略模块
  • label/icon - 用于登录页面。icon 可以是任何 FontAwesome 图标名称。
  • options - 传递给创建时护照策略的选项对象。请参考策略自己的文档以了解它所需的内容。请参见下面有关 callbackURLcallbackMethod 的说明。
  • verify - 策略所使用的验证函数。如果用户有效,它必须以用户配置文件作为第二个参数调用 done。预计该配置文件具有用于检查有效用户列表的 username 属性。护照尝试标准化用户配置文件对象,因此大多数策略提供此属性。
  • autoLogin - 布尔值,当为 true 时,将自动重定向到身份验证提供者,而不是要求用户单击按钮。

策略使用的 callbackURL 是身份验证提供者在进行身份验证尝试后重定向的地方。它必须是您的 Node-RED 编辑器 URL,并在路径中添加 /auth/strategy/callback。例如,如果您在 http://localhost:1880 访问编辑器,则应使用 http://localhost:1880/auth/strategy/callback

默认情况下,callbackURL 将监听 GET 请求。要改为使用 POST 请求,请将 callbackMethod 设置为 POST

设置默认用户

上述示例配置将阻止任何人访问编辑器,除非他们登录。

在某些情况下,希望允许每个人获得一定级别的访问权限。通常,这将是给予编辑器的只读访问权限。为此,可以将 default 属性添加到 adminAuth 设置,以定义默认用户:

adminAuth: {
    type: "credentials",
    users: [ /* 用户列表 */ ],
    default: {
        permissions: "read"
    }
}

用户权限

在 Node-RED 0.14 之前,用户可以拥有两种权限之一:

  • * - 完全访问
  • read - 只读访问

从 Node-RED 0.14 起,权限可以更细粒度,并且为了支持这一点,属性可以是单个字符串,或包含多个权限的数组。

Admin API 的每个方法定义访问所需的权限级别。权限模型是基于资源的。例如,要获取当前流程配置,用户将需要 flows.read 权限。但是,要更新流程,他们将需要 flows.write 权限。

令牌过期

默认情况下,访问令牌在创建后 7 天过期。目前不支持刷新令牌以延长此期限。

过期时间可以通过设置 adminAuth 设置的 sessionExpiryTime 属性自定义。这定义了,单位为秒,令牌的有效时长。例如,要设置令牌在 1 天后过期:

adminAuth: {
    sessionExpiryTime: 86400,
    ...
}

访问 Admin API

设置了 adminAuth 属性后,Admin API 文档 描述了如何访问 API。

自定义用户身份验证

与其将用户硬编码到设置文件中,不如可以插入自定义代码来验证用户。这使得与现有身份验证方案集成成为可能。

以下示例展示了如何使用外部模块提供自定义身份验证代码。

  • 将以下内容保存到一个名为 <node-red>/user-authentication.js 的文件中。
module.exports = {
   type: "credentials",
   users: function(username) {
       return new Promise(function(resolve) {
           // 执行所需的任何工作,以检查用户名是否有效用户。
           if (valid) {
               // 解析为用户对象。它必须包含 'username' 和 'permissions' 属性
               var user = { username: "admin", permissions: "*" };
               resolve(user);
           } else {
               // 解析为 null 以指示该用户不存在
               resolve(null);
           }
       });
   },
   authenticate: function(username,password) {
       return new Promise(function(resolve) {
           // 执行所需的任何工作,以验证用户名/密码组合。
           if (valid) {
               // 解析为用户对象。相当于调用 users(username);
               var user = { username: "admin", permissions: "*" };
               resolve(user);
           } else {
               // 解析为 null 以指示用户名/密码对无效。
               resolve(null);
           }
       });
   },
   default: function() {
       return new Promise(function(resolve) {
           // 解析为默认用户的用户对象。如果没有默认用户,则解析为 null。
           resolve({anonymous: true, permissions:"read"});
       });
   }
}
  • 在 settings.js 中设置 adminAuth 属性以加载此模块:
adminAuth: require("./user-authentication")

自定义身份验证令牌

自 Node-RED 1.1.0 起

在某些情况下,您可能需要使用自己的身份验证令牌,而不是使用 Node-RED 生成的令牌。例如:

  • 您希望使用基于 OAuth 的用户身份验证,但您还要求自动访问 admin API,而无法执行 OAuth 所需的交互式身份验证步骤。
  • 您希望将 Node-RED 集成到现有系统中,用户已登录,而您不希望他们在访问编辑器时再次登录。

adminAuth 设置可以包含 tokens 函数。如果对管理 API 的请求不包含 Node-RED 识别为其自己的身份验证令牌,则将调用此函数。它传递请求中提供的令牌,并且应该返回一个 Promise,该 Promise 解析为经过身份验证的用户,否则为 null(如果令牌无效)。

adminAuth: {
    ...
    tokens: function(token) {
        return new Promise(function(resolve, reject) {
            // 执行所需的任何工作以检查令牌是否有效
            if (valid) {
                // 解析为用户对象。它必须包含 'username' 和 'permissions' 属性
                var user = { username: 'admin', permissions: '*' };
                resolve(user);
            } else {
                // 解析为 null,因为该用户不存在
                resolve(null);
            }
        });
    },
    ...
}

默认情况下,它将使用 Authorization http 头并期望一个 Bearer 类型的令牌——仅将令牌的值传递给函数。如果不是 Bearer 类型的令牌,则将传递 Authorization 头的完整值,这将包含类型和值。

要使用不同的 HTTP 头,可以使用 tokenHeader 设置来确定使用哪个头:

adminAuth: {
    ...
    tokens: function(token) {
        ...
    },
    tokenHeader: "x-my-custom-token"
}
使用自定义令牌访问编辑器

要使用自定义令牌访问编辑器而不出现登录提示,请在 URL 中添加 ?access_token=<ACCESS_TOKEN>。编辑器将本地存储该令牌,并在所有未来请求中使用它。

HTTP 节点安全

HTTP In 节点暴露的路由可以使用基本身份验证进行保护。

在您的 settings.js 文件中,可以使用 httpNodeAuth 属性定义允许访问这些路由的单个用户名和密码。

httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."},

pass 属性使用与 adminAuth 相同的格式。有关更多信息,请参见 生成密码哈希

使用 httpStatic 属性定义的任何静态内容的访问可以使用 httpStaticAuth 属性进行保护,格式相同。

注意 : 在之前的 Node-RED 版本中,pass 属性预期为 MD5 哈希。由于密码学不安全,因此已被 bcrypt 取代, bcrypt 由 adminAuth 使用。为了向后兼容,MD5 哈希仍受支持——但不推荐使用。

自定义中间件

可以提供自定义 HTTP 中间件,它将添加到所有 HTTP In 节点前,自 Node-RED 1.1.0 起,添加到所有管理/编辑器路由前。

HTTP In 节点的自定义中间件

对于 HTTP In 节点,作为 httpNodeMiddleware 设置提供中间件。

以下设置是限制 HTTP In 节点中 HTTP 访问率的示例。

// 在 `~/.node-red/` 目录中提前运行 `npm install express-rate-limit`
var rateLimit = require("express-rate-limit");
module.exports = {
    httpNodeMiddleware: rateLimit({
        windowMs: 1000, // 设置窗口时间为 1000 毫秒。
        max: 10 // 将访问速率限制为 10 个请求/秒
    })
}

使用此配置,即使以 http-in 节点开头的流程需要一些处理时间,Node-RED 进程也可以避免内存耗尽。当达到限制时,端点将返回默认消息:“请求过多,请稍后再试。”

管理 API 的自定义中间件

对于管理/编辑器路由,提供的中间件作为 httpAdminMiddleware 设置。

例如,以下中间件可用于在所有管理/编辑器请求上设置 X-Frame-Options http 头。这可以用于控制编辑器如何嵌入到其他页面。

httpAdminMiddleware: function(req, res, next) {
    // 设置 X-Frame-Options 头以限制编辑器的嵌入位置
    res.set('X-Frame-Options', 'sameorigin');
    next();
},

其他可能的用途还包括为路由添加额外的安全层或请求验证。