保护 Node-RED:HTTPS、身份验证与访问控制

默认情况下,Node-RED 编辑器没有访问保护。任何能访问其 IP 地址的人都可以进入编辑器并部署更改。只有在可信网络中运行时,这样的默认设置才合适。本文介绍三部分保护:启用 HTTPS、保护编辑器与 Admin API,以及保护 HTTP 节点和静态内容。

配置在 settings.js 中完成,修改后需要让运行时加载新配置。

启用 HTTPS

在设置文件中提供 https 属性,其值是传给 Node.js HTTPS 服务器的选项对象。常见选项是 PEM 格式的 key 和 cert,可以是字符串或 Buffer;证书可以包含完整证书链。详见 Node.js HTTPS API。

静态证书示例:

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

从 Node-RED 1.1 起,https 也可以是返回选项对象的函数,或者返回一个解析为选项对象的 Promise:

https: function() {
    return new Promise((resolve, reject) => {
        var key, cert;
        // Do some work to obtain valid certificates
        // ...
        resolve({
            key: key,
            cert: cert
        });
    });
}

这只是获取证书的结构示例,必须补上实际读取或获取证书的逻辑。

刷新证书

从 Node-RED 1.1 起,配合 Node.js 11 或以上版本,可以不重启运行时而刷新 HTTPS 证书。https 必须是函数,并设置 httpsRefreshInterval,单位为小时。运行时定期重新调用该函数。若证书暂时不需要更新,可以返回 undefined 或 null,继续使用现有证书。

保护编辑器和 Admin API

adminAuth 为编辑器和 Admin API 启用身份验证。它支持用户名/密码以及 Passport 策略。

用户名和密码

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

users 是允许访问的用户数组。密码使用 bcrypt 哈希,不能直接填明文。示例中 admin 的密码是 password,仅用于演示,部署时应换成自己的强密码及其哈希。permissions: "*" 允许完整读写;"read" 只允许查看。

旧的 httpAdminAuth 设置已经弃用,不应再用于新配置。

生成密码哈希

Node-RED 1.1 起可以执行:

node-red admin hash-pw

较早版本可安装并使用 node-red-admin 的命令:

node-red-admin hash-pw

也可直接调用 bcryptjs:

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

OAuth/OpenID 等外部身份验证

可以使用 Passport 策略,包括 Twitter、GitHub 等。所需策略模块必须安装在 Node-RED 能加载到的位置。以下是 Twitter 策略示例,应用密钥应替换为实际配置:

adminAuth: {
    type: "strategy",
    strategy: {
        name: "twitter",
        label: 'Sign in with 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 是 Font Awesome 图标类。
  • options 直接传给策略构造函数,具体字段由策略决定。
  • verify 完成身份验证后调用 done,第二个参数提供用户档案;运行时需要从中取得用户名,再确定权限。

autoLogin: true 可以使登录页自动转向身份提供者。回调地址应为编辑器地址加 /auth/strategy/callback。默认使用 GET;需要 POST 回调的策略可设置 callbackMethod: "POST"。

匿名默认权限

可选的 default 指定未登录用户的权限。例如允许匿名读取,登录用户按用户列表获得权限:

adminAuth: {
    type: "credentials",
    users: [
        // list of users
    ],
    default: {
        permissions: "read"
    }
}

权限

在 Node-RED 0.14 之前,只支持 "*" 与 "read"。从 0.14 起,权限可以是字符串或数组,并可以对应 Admin API 的具体操作,例如 flows.read、flows.write。具体 API 所需权限见 Admin API。

令牌有效期

访问令牌默认有效七天,不会自动续期。sessionExpiryTime 以秒为单位覆盖该期限;例如设为 86400 表示一天。

自定义用户身份验证

可以把用户查询与验证交给外部系统。在 Node-RED 用户目录创建 user-authentication.js,导出如下对象:

module.exports = {
    type: "credentials",
    users: function(username) {
        return new Promise(function(resolve) {
            // Do whatever work is needed to check username is a valid
            // user.
            if (valid) {
                // Resolve with the user object. It must contain
                // properties 'username' and 'permissions'
                var user = { username: "admin", permissions: "*" };
                resolve(user);
            } else {
                // Resolve with null to indicate this user does not exist
                resolve(null);
            }
        });
    },
    authenticate: function(username, password) {
        return new Promise(function(resolve) {
            // Do whatever work is needed to validate the username/password
            // combination.
            if (valid) {
                // Resolve with the user object. Equivalent to users() above
                var user = { username: "admin", permissions: "*" };
                resolve(user);
            } else {
                // Resolve with null to indicate the username/password pair
                // were not valid.
                resolve(null);
            }
        });
    },
    default: function() {
        return new Promise(function(resolve) {
            // Resolve with the user object for the default user.
            // If no default user exists, resolve with null.
            resolve({ anonymous: true, permissions: "read" });
        });
    }
};

users() 负责根据用户名返回用户对象或 null;authenticate() 验证用户名/密码组合;可选 default() 返回匿名用户或 null。valid 是原文中的占位条件,需要自行实现验证逻辑,示例并非可直接运行的身份验证模块。设置文件引用该模块:

adminAuth: require("./user-authentication")

自定义令牌验证

从 Node-RED 1.1 起,可自定义访问令牌验证。这适用于外部 OAuth 令牌、自动化访问,或把编辑器嵌入已经登录的应用。

在 adminAuth 中提供 tokens 函数。当令牌不属于 Node-RED 自身发行的令牌时,运行时调用该函数。函数应返回 Promise,解析为包含 username、permissions 的用户对象,或在令牌无效时解析为 null:

adminAuth: {
    // ...
    tokens: function(token) {
        return new Promise(function(resolve, reject) {
            // Do whatever work is needed to check token is valid
            if (valid) {
                // Resolve with the user object. It must contain
                // properties 'username' and 'permissions'
                var user = { username: "admin", permissions: "*" };
                resolve(user);
            } else {
                // Resolve with null to indicate this user does not exist
                resolve(null);
            }
        });
    }
}

上述 valid 同样需要接入真实令牌验证。若请求头是 Authorization: Bearer ...,只传入令牌部分;采用其他 Authorization 格式时,传入完整头值。tokenHeader 可指定别的头,例如 "x-my-custom-token"。

在编辑器中传入令牌

在编辑器 URL 后添加 ?access_token=<ACCESS_TOKEN> 可以预先提供令牌。编辑器会将它保存到本地,供后续请求使用。

保护 HTTP 节点和静态内容

httpNodeAuth 为 HTTP In 节点提供 HTTP Basic 身份验证:

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

pass 是 bcrypt 哈希。httpStaticAuth 以同样方式保护 httpStatic 提供的静态内容。旧 MD5 哈希仍为兼容性而支持,但不推荐使用。

自定义 HTTP 中间件

httpNodeMiddleware 用于 HTTP In 节点,Node-RED 1.1 起的 httpAdminMiddleware 用于编辑器和 Admin API。它们是 Express 中间件,可做额外验证或限制。

例如使用 express-rate-limit 限制请求频率,减少内存耗尽风险。先在 ~/.node-red/ 安装所需模块,再配置:

// Run `npm install express-rate-limit` on `~/.node-red/` directory in advance
var rateLimit = require("express-rate-limit");
module.exports = {
    httpNodeMiddleware: rateLimit({
        windowMs: 1000,
        max: 10
    })
}

这个示例每秒允许最多 10 次请求,超过限制时中间件返回默认的 Too Many Requests 响应。模块接口须与所安装版本匹配。

编辑器侧也可以设置响应头,例如限制同源页面嵌入:

httpAdminMiddleware: function(req, res, next) {
    res.set('X-Frame-Options', 'sameorigin');
    next();
},

也可以在这些中间件中接入应用所需的其他验证逻辑。


原文:Securing Node-RED。Node-RED 项目及贡献者;中文翻译。网站源码采用 Apache License 2.0,原版权含 © 2016 JS Foundation and other contributors、© 2013, 2016 IBM Corp.;许可、版权及修改记录应随转载保留。示例账户、哈希和令牌均来自公开文档,不代表可用的私有凭据。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容