From a366bdbf02cc2b5f4d1654bbd776a50c1dab18f6 Mon Sep 17 00:00:00 2001 From: Alexey Kryuchkov Date: Fri, 2 Jun 2023 00:00:30 +0300 Subject: [PATCH] Add server option 'JsonRpcWebApiAllowedSubnet' to restrict access to JSON-RPC API based on client IP address --- .../vpnserver-jsonrpc-clients/README.html | 1 + .../vpnserver-jsonrpc-clients/README.md | 1 + .../Templates/doc.txt | 1 + src/Cedar/Protocol.c | 18 ++++++++++++++---- src/Cedar/Server.c | 15 +++++++++++++++ src/Cedar/Server.h | 3 +++ src/Mayaqua/Network.c | 12 ++++++++++++ src/Mayaqua/Network.h | 1 + 8 files changed, 48 insertions(+), 4 deletions(-) diff --git a/developer_tools/vpnserver-jsonrpc-clients/README.html b/developer_tools/vpnserver-jsonrpc-clients/README.html index 637f0c6b..2f89f8d7 100644 --- a/developer_tools/vpnserver-jsonrpc-clients/README.html +++ b/developer_tools/vpnserver-jsonrpc-clients/README.html @@ -30,6 +30,7 @@

JSON-RPC specification

You must use HTTPS 1.1 POST method to call each of JSON-RPC APIs.
diff --git a/developer_tools/vpnserver-jsonrpc-clients/README.md b/developer_tools/vpnserver-jsonrpc-clients/README.md index 22866867..71140d3f 100644 --- a/developer_tools/vpnserver-jsonrpc-clients/README.md +++ b/developer_tools/vpnserver-jsonrpc-clients/README.md @@ -25,6 +25,7 @@ https://:/api/ - Older versions of SoftEther VPN before June 2019 don't support JSON-RPC APIs. - If you want to completely disable the JSON-RPC on your VPN Server, set the `DisableJsonRpcWebApi` variable to `true` on the `vpn_server.config`. + - You may also restrict access to JSON-RPC API to a specific subnet, e.g. your internal network, by setting the `JsonRpcWebApiAllowedSubnet` variable to, for example, `192.168.0.0/16`. ### JSON-RPC specification diff --git a/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt b/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt index aed5c743..45be851d 100644 --- a/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt +++ b/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt @@ -25,6 +25,7 @@ https://:/api/ - Older versions of SoftEther VPN before June 2019 don't support JSON-RPC APIs. - If you want to completely disable the JSON-RPC on your VPN Server, set the `DisableJsonRpcWebApi` variable to `true` on the `vpn_server.config`. + - You may also restrict access to JSON-RPC API to a specific subnet, e.g. your internal network, by setting the `JsonRpcWebApiAllowedSubnet` variable to, for example, `192.168.0.0/16`. ### JSON-RPC specification diff --git a/src/Cedar/Protocol.c b/src/Cedar/Protocol.c index e2efd2b9..e0c7ad3f 100644 --- a/src/Cedar/Protocol.c +++ b/src/Cedar/Protocol.c @@ -5763,6 +5763,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) UINT num = 0, max = 19; SERVER *server; char *vpn_http_target = HTTP_VPN_TARGET2; + bool disableJsonRpcWebApi; // Validate arguments if (c == NULL) { @@ -5773,6 +5774,15 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) s = c->FirstSock; + disableJsonRpcWebApi = server->DisableJsonRpcWebApi; + if (!disableJsonRpcWebApi && !IsZeroIP(&server->JsonRpcWebApiAllowedSubnetAddr) + && !IsZeroIP(&server->JsonRpcWebApiAllowedSubnetMask)) { + // restrict JSON-RPC Web API to specified subnet only + if (!IsInSameNetwork(&s->RemoteIP, &server->JsonRpcWebApiAllowedSubnetAddr, &server->JsonRpcWebApiAllowedSubnetMask)) { + disableJsonRpcWebApi = true; + } + } + while (true) { bool not_found_error = false; @@ -5805,7 +5815,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) // Receive the data since it's POST data_size = GetContentLength(h); - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { if (StrCmpi(h->Target, "/api") == 0 || StrCmpi(h->Target, "/api/") == 0) { @@ -5891,7 +5901,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) } else if (StrCmpi(h->Method, "OPTIONS") == 0) { - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { if (StrCmpi(h->Target, "/api") == 0 || StrCmpi(h->Target, "/api/") == 0 || StartWith(h->Target, "/admin")) { @@ -5962,7 +5972,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) BUF *b = NULL; *error_detail_str = "HTTP_ROOT"; - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { b = ReadDump("|wwwroot/index.html"); } @@ -6042,7 +6052,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) if (b == false) { - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { if (StartWith(h->Target, "/api?") || StartWith(h->Target, "/api/") || StrCmpi(h->Target, "/api") == 0) { diff --git a/src/Cedar/Server.c b/src/Cedar/Server.c index 1aad0d94..6eb53d44 100644 --- a/src/Cedar/Server.c +++ b/src/Cedar/Server.c @@ -30,6 +30,7 @@ #include "Mayaqua/Internat.h" #include "Mayaqua/Memory.h" #include "Mayaqua/Microsoft.h" +#include "Mayaqua/Network.h" #include "Mayaqua/Object.h" #include "Mayaqua/OS.h" #include "Mayaqua/Pack.h" @@ -6032,6 +6033,15 @@ void SiLoadServerCfg(SERVER *s, FOLDER *f) // Disable JSON-RPC Web API s->DisableJsonRpcWebApi = CfgGetBool(f, "DisableJsonRpcWebApi"); + char tmpaddr[MAX_PATH]; + if (CfgGetStr(f, "JsonRpcWebApiAllowedSubnet", tmpaddr, sizeof(tmpaddr))) { + IP _subnet, _mask; + if (ParseIpAndMask46(tmpaddr, &_subnet, &_mask)) { + s->JsonRpcWebApiAllowedSubnetAddr = _subnet; + s->JsonRpcWebApiAllowedSubnetMask = _mask; + } + } + // Bits of Diffie-Hellman parameters c->DhParamBits = CfgGetInt(f, "DhParamBits"); if (c->DhParamBits == 0) @@ -6365,6 +6375,11 @@ void SiWriteServerCfg(FOLDER *f, SERVER *s) // Disable JSON-RPC Web API CfgAddBool(f, "DisableJsonRpcWebApi", s->DisableJsonRpcWebApi); + + char tmpaddr[MAX_PATH]; + IPAndMaskToStr(tmpaddr, sizeof(tmpaddr), + &s->JsonRpcWebApiAllowedSubnetAddr, &s->JsonRpcWebApiAllowedSubnetMask); + CfgAddStr(f, "JsonRpcWebApiAllowedSubnet", tmpaddr); } Unlock(c->lock); } diff --git a/src/Cedar/Server.h b/src/Cedar/Server.h index 01fbddf7..82e8a22c 100644 --- a/src/Cedar/Server.h +++ b/src/Cedar/Server.h @@ -276,6 +276,9 @@ struct SERVER IP ListenIP; // Listen IP bool StrictSyslogDatetimeFormat; // Make syslog datetime format strict RFC3164 bool DisableJsonRpcWebApi; // Disable JSON-RPC Web API + + IP JsonRpcWebApiAllowedSubnetAddr; // If set, allow access to JSON-RPC Web API from + IP JsonRpcWebApiAllowedSubnetMask; // this subnet only }; diff --git a/src/Mayaqua/Network.c b/src/Mayaqua/Network.c index 6cc39318..40a2b39d 100644 --- a/src/Mayaqua/Network.c +++ b/src/Mayaqua/Network.c @@ -6976,6 +6976,18 @@ void IPToStr6Inner(char *str, IP *ip) } } +// Format IP and subnet mask as "/" +void IPAndMaskToStr(char *str, UINT size, IP *ip, IP *subnet) +{ + int iplen; + UINT masksize; + + IPToStr(str, size, ip); + iplen = StrLen(str); + masksize = SubnetMaskToInt(subnet); + Format(str + iplen, size - iplen, "/%d", masksize); +} + // Convert the string to an IP address bool StrToIP6(IP *ip, char *str) { diff --git a/src/Mayaqua/Network.h b/src/Mayaqua/Network.h index 202df410..b41282c3 100644 --- a/src/Mayaqua/Network.h +++ b/src/Mayaqua/Network.h @@ -1270,6 +1270,7 @@ void IPToStr6(char *str, UINT size, IP *ip); void IP6AddrToStr(char *str, UINT size, IPV6_ADDR *addr); void IPToStr6Array(char *str, UINT size, UCHAR *bytes); void IPToStr6Inner(char *str, IP *ip); +void IPAndMaskToStr(char *str, UINT size, IP *ip, IP *subnet); void IntToSubnetMask6(IP *ip, UINT i); void IPAnd6(IP *dst, IP *a, IP *b); void GetAllRouterMulticastAddress6(IP *ip);