确保Kamailio通过HTTP传输的JSON-RPC安全

我在上一篇文章中介绍了 Kamailio 的 JSCON-RPC over HTTP,旨在介绍 RPC 的使用方法,但被指出不安全且不适合生产。现在,让我们看看如何纠正这一问题。本文章的更新配置可在此处找到:

https://github.com/whosgonna/kamailio-httprpc/tree/main/ex2-secure

这个较新的示例将添加:

  • 仅通过专用侦听端口接受 HTTP。然后,该端口只能暴露给本地网络,人们将无法通过 Internet 在您的 TCP SIP 端口上访问管理服务。
  • 使用摘要式身份验证以及预设的用户名和密码来控制访问。
  • 限制公开的 RPC 方法的数量。即使 LAN 上的某人连接到服务器,他们也无法停止 Kamailio 或检索预处理器定义等。

唯一推荐的其他方法是使用 TLS,但这超出了本文的范围。

与之前版本的差异

那么,有什么不同呢?首先,比之前的版本长很多,有近 100 行,而之前的版本有 19 行。当然它还包含更多的功能。

用户名和密码定义

首先,它从环境变量加载用户名和密码:

#!KAMAILIO

#!defenv RPC_USER
#!defenv RPC_PASS

随附的 docker-compose 文件确实为这些设置了默认值,但也可以通过 .env 文件等进行覆盖。

附加模块

第一个最小模型只使用了 sl、json、xhttp 和 jsonrpcs。这个版本加载了更多的模块:

loadmodule "pv"
loadmodule "xlog"
loadmodule "sl"
loadmodule "json"
loadmodule "xhttp"
loadmodule "jsonrpcs"
loadmodule "tcpops"
loadmodule "htable"
loadmodule "auth"
loadmodule "kex"

这些都不是什么稀奇事。我们添加了 xlog 模块,以便在 Kamailio 的输出中为自己提供一些信息。auth 模块为我们提供了用户身份验证功能,我们将在 htable 中定义允许在 JSON-RPC over HTTP 上使用的方法。之所以添加 kex 模块,是因为它有用于获取服务器统计数据的 RPC 方法,我们将把它作为一个允许使用的示例。

网络配置

由于我们希望 HTTP 接口使用默认 SIP 端口 5060 以外的端口,因此需要定义侦听器。这是对上一个示例中定义的 TCP 参数的补充:

tcp_accept_no_cl = yes
http_reply_parse = yes

listen = udp:eth0:5060
listen = tcp:eth0:5060

socket_workers = 2
listen         = tcp:eth0:8081

listen端口的定义在8081定义方式上没有什么特别之处,除了它使用 TCP 之外。之所以socket_workers减少,是因为虽然生产 Kamailio 服务器可能会处理大量 SIP 流量,但它可能不会处理太多 http 流量(不支持 Web 套接字)。

模块参数

modparam("jsonrpcs", "transport", 0)
modparam("htable",   "htable",   "rpc_allow=>size=4");

jsonrpcs 模块的传输参数与之前相同(0 – 使用所有传输)。添加了名为 rpc_allow 的 htable。文件中的 htable:mod-init 事件路由设置了它的默认配置。

请求路由

就像前面的例子一样,这个路由并不是用来处理任何 SIP 流量的,它只是一个出口:

request_route {
    exit;
}

HTTP 请求事件路由

这里的情况开始变得有点有趣,但为了更好地阅读和理解,我们并没有在这条路径中加入大量逻辑,而是将其划分为不同的路径。每个路由要么通过检查并返回,要么失败并退出。如果所有检查都通过并返回,则调用 jsonrpc_dispatch(),执行 RPC 请求。

event_route[xhttp:request] {
    route(XHTTP_PORT_CHECK);

    route(XHTTP_RPC_AUTH);

    route(VALIDATE_RPC_METHOD);

    jsonrpc_dispatch();
}

端口检查

这很简单。如果我们在 8081 端口以外的端口收到 TCP 检查,我们就会关闭套接字。我们不会发送任何响应 – 任何人都没有理由向 5060 端口发送 HTTP 请求,所以我们甚至不会响应。

route[XHTTP_PORT_CHECK] {
    if ( $Rp != 8081 ) {
        xcrit("HTTP Request not received on port 8081. reject\n");
        tcp_close_connection();
        exit;
    }
}

用户身份验证

这是配置文件中最复杂的部分,其中包含几项内容。

首先,我们要检查是否提供了用户名以及用户名是否正确。如果没有提供用户名,我们将继续路由,但如果给出的用户名与已知用户不匹配,我们将回复 403 未授权。不使用 sl_send_reply() 或 send_reply(),而是使用 http_reply()。这样就可以在消息中定义 Content-Type 头和正文。如果我们想花时间写出 JSON 对象(并使用 application/json 而不是 text/plain 的内容类型),那么在 200 OK 中格式化适当的 JSON-RPC 失败消息可能会更合适。

接下来,将使用 pv_www_authenticate()检查凭据,它允许我们输入静态realm 和密码进行验证。它可以返回多个值,但我们只对其中几个感兴趣。

首先,也许最明显的是 pv_www_authenticate 返回 true 且用户已通过验证的情况。consumer_credentials() 会将凭据从消息中删除,以免它们在下一跳中被带到下游。由于这是对调用者的回复,因此严格来说可能并无必要,但却是一个很好的做法。

如果 pv_www_authenticate()失败,我们会检查返回代码(存储在 $rc),以了解失败原因。如果返回值为 -2,则表示密码不正确。就像用户不正确一样,我们将发送 403 Unauthorized(未授权)响应。事实上,这是一个完全相同的响应,因为我们并不想说明失败的原因是用户名还是密码。请求者只需知道其中至少有一个不正确即可。

如果没有收到用户名和密码(这对第一次请求来说是正常的),我们将使用 www_challenge() 发出验证挑战。这样,客户端就会知道预期的境界是什么。

对于其他任何失败,我们都会返回一个更普通的 503 服务器错误信息。在这个示例中,我们没有必要进一步研究它。

route[XHTTP_RPC_AUTH]{
    if ( $au != $null && $au != $def(RPC_USER) ) {
        xerr("Invalid RPC user : [$au].\n");
        xhttp_reply(
            "403", "Unauthorized", "text/plain", "403 Unauthorized\r\n"
        );
        exit;
    }


    if ( !pv_www_authenticate("$Ri", "$def(RPC_PASS)", "0") ) {
        switch ( $rc ) {
            case -2:
                xerr("Invalid RPC password for user $au\n");
                xhttp_reply(
                    "403", "Unauthorized", "text/plain", "403 Unauthorized\r\n"
                );
                exit;
            case -5:
                xinfo("HTTP request with no crednetials. Send challenge\n");
                www_challenge("$Ri", "0");
                exit;
            default:
                xinfo("Misc. WWW auth failure. $$rc [$rc]\n");
                xhttp_reply(
                    "503", "Server Error", "text/plain", "503 Server Error\r\n"
                );
                exit;
        }
    }

    consume_credentials();
}

检查 RPC 方法是否允许

验证请求后,我们执行请求前的最后一步是检查 RPC 方法是否已被允许。这里显示了两个路径。VALIDATE_RPC_METHOD 进行实际检查。htable:mod-init 事件路由在 Kamailio 启动时运行,以填充 rpc_allow htable。

route[VALIDATE_RPC_METHOD] {
    json_get_field("$rb", "method", "$avp(rpc_method)");
    $avp(rpc_method) = $(avp(rpc_method){s.unquote});

    if ( $sht(rpc_allow=>$avp(rpc_method)) == 1 ) {
        return;
    }

    xhttp_reply("403", "Not Allowed", "text/plain", "Not Allowed\n");
    exit;
}

event_route[htable:mod-init] {
    $sht(rpc_allow=>stats.fetch)  = 1;
    $sht(rpc_allow=>core.version) = 1;
    $sht(rpc_allow=>mod.stats)    = 1;
}

json_get_field() 会从请求中提取方法。然后,我们检查该方法是否在 rpc_allow htable 中。如果是,我们就返回并执行请求。如果不在,则向客户端返回 403 Not Allowed。

使用方法

既然我们已经介绍了配置,那么该如何使用它呢?让我们来看几个curl示例:

获取Kamailio版本(Get Kamailio Version)–与上一篇文章相同,但使用了验证参数。注意必须定义–digest:

[kaufmania]$ curl -X POST \
    -d '{"jsonrpc":"2.0","method":"core.version","id":"1"}' \
    -s --digest -u rpcuser:rpcpass  \
    http://localhost:8081
{
        "jsonrpc":      "2.0",
        "result":       "kamailio 5.6.3 (x86_64/linux) ",
        "id":   "1"
}

当然,如果我们想检查错误的凭据以确认身份验证检查是否正常工作:

[kaufmania]$ curl -X POST \
    -d '{"jsonrpc":"2.0","method":"core.version","id":"1"}' \
    -s --digest -u rpcuser:badpass  \
    http://localhost:8081
403 Unauthorized

如果我们尝试其中一种不允许的方法(例如关闭服务器)怎么办?

[kaufmania]$ curl -X POST \
    -d '{"jsonrpc":"2.0","method":"core.kill","id":"1"}' \
    -s --digest -u rpcuser:rpcpass  \
    http://localhost:8081
Not Allowed

如果我们想查看统计数据?

[kaufmania]$ curl -X POST \
    -d '{"jsonrpc":"2.0","method":"stats.fetch","id":"1","params":["all"]}' \
    -s --digest -u rpcuser:rpcpass  \
    http://localhost:8081
{
        "jsonrpc":      "2.0",
        "result":       {
                "core.bad_URIs_rcvd":   "0",
                "core.bad_msg_hdr":     "0",
                "core.drop_replies":    "0",
                "core.drop_requests":   "0",
                "core.err_replies":     "0",
                "core.err_requests":    "0",
. . . 

结论

这就构成了在实际应用中可行的 HTTP RPC 配置。发送命令以重新加载特定的Kamailio配置或以编程方式远程检索特定数据的能力极具价值,凸显了Kamailio的灵活性。

原文:https://kaufmania.wordpress.com/2023/09/13/securing-kamailios-json-rpc-over-http/

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/35242.html

(0)

相关推荐

发表回复

登录后才能评论