提升安全性:Nginx 反向代理 RDP 和 IIS 服务
后续:将提供 webdav 服务的 IIS 替换为 rclone
关联:使用 Zram 作为 Swap,将内存消耗从 750MB 降至 450MB,教程:how-to-enable-the-zram-module-for-faster-swapping-on-linux
Windows 远程桌面服务作为开放给外网的高权限系统组件存在很大的安全隐患,因此如果需要使用 RDP 服务,最好不要直接开放其对应的 3389 端口。通过Remote Desktop Gateway这个默认存在于rdp客户端的认证通道或者是通过代理来避免端口直接暴露。以上两种方式中前者较麻烦,后者则造成每次连接远程桌面都要开启代理,都不太方便。本文介绍如何用Guacamole将rdp内容转换为HTTP,再由Nginx代理给客户端来减少暴露额外的端口带来的安全风险。Nginx监听 443 端口传入的 HTTPS 协议内容,并按路径转发给 Hyper-v VM Docker 中的Guacamole和 Windows 上的 IIS 服务端。Guacamole将宿主机的 RDP 服务转换成 HTML5 内容,不再需要服务端向局域网开放 3389 端口。
本文基于如下环境和程序
| Environment | Contains | |
|---|---|---|
| Windows | IIS Webserver 10.0 | Hyper-v (VM) |
| Debian10 VM (Hyper-v) | Nginx 1.14.2 | Docker (Apache Guacamole 1.2.0) |
各程序服务按外部访问流程排序
| Program | Usage | Env | Protocol | Address |
|---|---|---|---|---|
| Nginx | HTTPS reverse proxy | Debian10 VM | HTTPS | 192.168.51.118:443 Lan 192.168.208.146 VM |
| IIS Webserver | Other web app (Webdav) | Windows | HTTP | 192.168.208.145:8088/S4f_/ VM |
| Guacamole | HTML5 RDP server | Debian10 VM (Docker) | HTTP | 127.0.0.1:8080/rdp/ VM |
| RDP | Microsoft remote desktop | Windows | RDP | 192.168.208.145:3389 VM |
覆盖图
graph LR
subgraph "Hyper-V"
subgraph "VM (Debian10)"
c1[Nginx]-->|127.0.0.1
:8080/rdp/|c2[Guacamole
& SSH to self]
end
subgraph "Windows10"
a1["IIS Webserver
(WebDav)"]
a2[RDP]
end
end
b1[用户]-->|HTTPS
192.168.51.118|c1
c1-->|192.168.208.145
:8088/S4f_/|a1
c2-->|192.168.208.145
:3389|a2
No Need To Prepare Config File #
More offcial Docs Here
This Part is for the native build therefore SHOULD BE SKIPPED.1
As we use docker file here, the default user has been set to guacadmin with password guacadmin.
Open Firewall for IIS’ Unusual Port #
In Windows
After binding port 8088 to IIS server’s http service, there are addtional steps for unusual port other than 80 and 443.
Open firewall advance settings, add allow rules for 8080. In Scope Remote IP address part, add ip range 192.168.208.0 to 192.168.208.255. In Advance Profiles part, check all three checkboxs and confirm.
Install Guacamole by Docker #
In Hyper-v (Debian10)
Docker Site and uploader’s Github
- Add those text to a
Dockerfilewhich is used for changing base url.2
| |
- Run command at the place where you put the
Dockerfileto bulid a new image.
| |
- Run the container (replace
/root/guacamolewith your config directory which shouldmkdirfirst when new build) from your new-built image.
| |
Nginx URL Rewrite #
In Hyper-v (Debian10)
My original plan is to use IIS ARR3.0 module to achive the URL rewrite purpose as this IIS webserver also do the job of sharing my files in Lan via Webdav. But after a query online, I find out that the support of Websocket in IIS which Guacamole needed to communicate seems lack. Therefore I use Nginx to do reverse proxy both for Guacamole in Hyper-V and Webdav that host on IIS.3
Assign IPs to VM 4 #
To get double IPs in debian VM, first you need to add two network interfaces (here for example eth0 is external for Lan and the eth1 is internal to host) on hyper-v. Then login to the VM and add those lines to /etc/network/interfaces, eth1’s static ip is different from the host 192.168.208.145 (like one number after) and mask is same as host’s (you can find those in Windows Cmd ipconfig /all). This internal ip is related to firewall settings in Windows so you need to assign one manually to access the host Windows:
| |
Apply changes by systemctl restart networking. Type ip a in command to find your IPs.
In Windows
To add static IP in Windows, open Cmd(Administrator) and input:
| |
Add this command to Manage->Scheduled Tasks->Create a Basic Task and set it run as administrator to get static ip rather than random one that Windows assign to on startup. Disable only start task when plug in power and add a delay for 30s after startup.
By default, Hyper-v will give VM a random Mac which would make your router assign new ip for your VM’s external network on boot. Open VM’s settings, expand your external Network Adapter and click on Advanced Features. In Mac Address select static and input a Mac address.
Install Nginx with Webdav module #
In Hyper-v (Debian10)
| |
Remove Default Page
| |
Add Proxy Config #
Add those lines to your /etc/nginx/nginx.conf file inside the server section.
Replace rdp with your guacamole’s url path.
| |
Replace S4f_ with your IIS Webdav url path, and 192.168.208.145 with your Windows’s internal ip. I also changed the query string from client to IIS to fix problems (it’s an Nginx bug when reverse proxy a webdav request, especially when use MOVE method, the Destination header would be set as nginx’s address rather than IIS’ 5) by .if set
add in http
| |
add in server
| |
Config HTTPS and Auth #
About how to generate self-signed certificate, you can refer to Here.
Copy your server.crt and server.key to nginx/ folder (use scp /path/to/file username@ip.ip.ip.ip:/path/to/destination or Winscp).
You can add extra auth for anyone who visit the site by nginx’s basic auth6.
Replace server config with following lines.
| |
And to disable default nginx index page, add in server:
| |
As a summary, your nginx.conf file should look like this:
| |
When done, restart your Nginx service:
| |
Manage Nginx and Guacamole to Autostart #
For Nginx:
| |
To check port use lsof -i -P.The Nginx log is in /var/log/nginx/ as access.log and error.log for debug.
For Guacamole, in /etc/systemd/system/docker-win10rdp.service add:
| |
Enable the service:
| |
Edit Guacamole Settings #
In Browser
Add Account #
Access Guacamole’s web page using your VM’s external ip address https://192.168.51.118/rdp with guacadmin for both username and password.
After a successfully login, open settings and click on the Users. Create your own account with all permissions checked. Sign in your own account and delete the default guacadmin one.
Add Connections #
Click on the New Connection inside the Connections. Fill the Name, Protocol, Hostname (Hyper-v internal ip), Port, Username, Password, Security Mode and check the Ignore server certificate. Save the changes.
In Hyper-v (Debian10)
If you want to enhance VM server’s security, you can add a ssh connection from docker to host. Use ip a to check docker0 host’s ip. Also, change the settings in /etc/ssh/sshd_config by change port and listenAddress. Apply changes sudo systemctl restart sshd:
| |
Also add these command after sudo systemctl edit sshd to allow sshd start when docker0’s ready:
| |
Test on Connection #
In Browser
Back to Home, click on the connection you just added. Check if everything is fine.
More Security Stuff #
Expose 3389 to Hyper-v only #
In Windows
Open firewall advance settings, add two allow rules for TCP and UDP port3389. In Scope Remote IP address part, add ip range 192.168.208.0 to 192.168.208.255. In Advance Profiles part, check all three checkboxs and confirm. Disable old TCP and UDP port 3389 allow rules.
Fail x00/x03 to Ban #
In Hyper-v (Debian10)
Install fail2ban
| |
Edit your config file from template
| |
Add your client ip to ignoreip
| |
Modify default bantime, findtime and maxretry
| |
Create your special jail in jail.local
| |
Create filter for Nginx x0x (like 403) error
| |
Add
| |
You can test your regex here
| |
Restart fail2ban
| |
To see status
| |
To rescue from jail
| |
APPENDIX #
Deprecated
Changing the config of Guacamole is now in its GUI control panel instead of in its config file.First, prepare the config file that will be used by
guacamole.
Replace192.168.208.145with yourWindowsIP address orHostname(not recommend) connected to theHyper-v(as192.168.208.146is the VM’s IP thatHyper-vassigned to). Also don’t forget to change theusernameandpassword (md5).
InDebian, you can usemd5sum <File contains PASSWORD>to hash your plain password.
/root/guacamole/user-mapping.xml ```xml
↩︎<!-- Another user, but using md5 to hash the password (example below uses the md5 hash of "PASSWORD") --> <authorize username="admin" password="**PASSWORD CONVERT TO MD5**" encoding="md5"> <connection name="Unique Name"> <protocol>rdp</protocol> <param name="hostname">192.168.208.145</param> <param name="port">3389</param> </connection> </authorize> </user-mapping> ```Failed
Failed onCOPY root /.(This file is originated from
oznu/guacamole, however the path incurl -SLo ${CATALINA_HOME}/webapps/ROOT.warhas been changed to${CATALINA_HOME}/webapps/rdp.warwhich is same tohttps://.../rdp).
↩︎```dockerfile FROM library/tomcat:9-jre11 ENV ARCH=amd64 \ GUAC_VER=1.2.0 \ GUACAMOLE_HOME=/app/guacamole \ PG_MAJOR=9.6 \ PGDATA=/config/postgres \ POSTGRES_USER=guacamole \ POSTGRES_DB=guacamole_db # Apply the s6-overlay RUN curl -SLO "https://github.com/just-containers/s6-overlay/releases/download/v1.20.0.0/s6-overlay-${ARCH}.tar.gz" \ && tar -xzf s6-overlay-${ARCH}.tar.gz -C / \ && tar -xzf s6-overlay-${ARCH}.tar.gz -C /usr ./bin \ && rm -rf s6-overlay-${ARCH}.tar.gz \ && mkdir -p ${GUACAMOLE_HOME} \ ${GUACAMOLE_HOME}/lib \ ${GUACAMOLE_HOME}/extensions WORKDIR ${GUACAMOLE_HOME} # Install dependencies RUN apt-get update && apt-get install -y \ libcairo2-dev libjpeg62-turbo-dev libpng-dev \ libossp-uuid-dev libavcodec-dev libavutil-dev \ libswscale-dev freerdp2-dev libfreerdp-client2-2 libpango1.0-dev \ libssh2-1-dev libtelnet-dev libvncserver-dev \ libpulse-dev libssl-dev libvorbis-dev libwebp-dev libwebsockets-dev \ ghostscript postgresql-${PG_MAJOR} \ && rm -rf /var/lib/apt/lists/* # Link FreeRDP to where guac expects it to be RUN [ "$ARCH" = "armhf" ] && ln -s /usr/local/lib/freerdp /usr/lib/arm-linux-gnueabihf/freerdp || exit 0 RUN [ "$ARCH" = "amd64" ] && ln -s /usr/local/lib/freerdp /usr/lib/x86_64-linux-gnu/freerdp || exit 0 # Install guacamole-server RUN curl -SLO "http://apache.org/dyn/closer.cgi?action=download&filename=guacamole/${GUAC_VER}/source/guacamole-server-${GUAC_VER}.tar.gz" \ && tar -xzf guacamole-server-${GUAC_VER}.tar.gz \ && cd guacamole-server-${GUAC_VER} \ && ./configure \ && make -j$(getconf _NPROCESSORS_ONLN) \ && make install \ && cd .. \ && rm -rf guacamole-server-${GUAC_VER}.tar.gz guacamole-server-${GUAC_VER} \ && ldconfig # Install guacamole-client and postgres auth adapter RUN set -x \ && rm -rf ${CATALINA_HOME}/webapps/ROOT \ && curl -SLo ${CATALINA_HOME}/webapps/rdp.war "http://apache.org/dyn/closer.cgi?action=download&filename=guacamole/${GUAC_VER}/binary/guacamole-${GUAC_VER}.war" \ && curl -SLo ${GUACAMOLE_HOME}/lib/postgresql-42.1.4.jar "https://jdbc.postgresql.org/download/postgresql-42.1.4.jar" \ && curl -SLO "http://apache.org/dyn/closer.cgi?action=download&filename=guacamole/${GUAC_VER}/binary/guacamole-auth-jdbc-${GUAC_VER}.tar.gz" \ && tar -xzf guacamole-auth-jdbc-${GUAC_VER}.tar.gz \ && cp -R guacamole-auth-jdbc-${GUAC_VER}/postgresql/guacamole-auth-jdbc-postgresql-${GUAC_VER}.jar ${GUACAMOLE_HOME}/extensions/ \ && cp -R guacamole-auth-jdbc-${GUAC_VER}/postgresql/schema ${GUACAMOLE_HOME}/ \ && rm -rf guacamole-auth-jdbc-${GUAC_VER} guacamole-auth-jdbc-${GUAC_VER}.tar.gz # Add optional extensions RUN set -xe \ && mkdir ${GUACAMOLE_HOME}/extensions-available \ && for i in auth-ldap auth-duo auth-header auth-cas auth-openid auth-quickconnect auth-totp; do \ echo "http://apache.org/dyn/closer.cgi?action=download&filename=guacamole/${GUAC_VER}/binary/guacamole-${i}-${GUAC_VER}.tar.gz" \ && curl -SLO "http://apache.org/dyn/closer.cgi?action=download&filename=guacamole/${GUAC_VER}/binary/guacamole-${i}-${GUAC_VER}.tar.gz" \ && tar -xzf guacamole-${i}-${GUAC_VER}.tar.gz \ && cp guacamole-${i}-${GUAC_VER}/guacamole-${i}-${GUAC_VER}.jar ${GUACAMOLE_HOME}/extensions-available/ \ && rm -rf guacamole-${i}-${GUAC_VER} guacamole-${i}-${GUAC_VER}.tar.gz \ ;done ENV PATH=/usr/lib/postgresql/${PG_MAJOR}/bin:$PATH ENV GUACAMOLE_HOME=/config/guacamole WORKDIR /config COPY root / EXPOSE 8080 ENTRYPOINT [ "/init" ] ```Deprecated
Use nginx reverse proxy instead because ARR’s lack of Websocket support.Install Application Request Routing (ARR) Module
InstallARR 3.0forReverse Proxysupport. You can install the ARR module directly from the location: http://www.microsoft.com/web/gallery/install.aspx?appid=ARRv3_0.Change URL Rewrite Settings
Open theIIS Managerand open theDefault Web Siteconfig pannel. Then double click on theURL Rewriteoption.
Add 2 rules forInbound rulesand another one forOutbound Rulesby clicking on theAdd Rule(s)...action and Choose theBlank Ruletemplate. Contents that needed to be changed are in the following tables:Inbound Rules
The first line of the table prevent address/S4f_/(another web app that hosted on the IIS server) to be rewritten to therdpsevice. Also need to check the Append query string checkbox.
↩︎|Name|Input|Match|Pattern|Action Type|Action URL|Stop Processing| |-|-|-|-|-|-|-| |S4f| - |match|S4f_(/?)(.*)|Rewrite|{R:0}|True| |rdp| - |match|rdp(/?)(.*)|Rewrite|http://172.17.111.38:8080/{R:0}|True|Deprecated
The hostname that Hyper-v provided is not stable. It’s better set static ip both manually.The old host-accessed-by-hostname (
HOSTNAME.mshome.net) plan isn’t unstable enough than assigning static ip to both host192.168.208.145and VM192.168.208.146. ↩︎Important
There is a bug when Nginx handle reverse proxy to a webdav server. Related discussion and webdav path requirement.In Nginx’s reverse proxy part
S4f_, I add extra filter to change query uri for webdav service. I’m usingrcloneas my Webdav client,however, it contains bugs while sending requests to a proxyed server. For example,rclonesends webdav methodMOVEand query urihttps://<nginx>/S4f_/path/to/fileto the server to rename a file while the correct query uri without proxy ishttp://<IIS>/S4f_/path/to/file.Therefore, to fix the problem, I add extra if-statement and regex.setto convert path that contains scheme and server name to relative path
Edit1: It seems that it’s a common problem when use Nginx as reverse proxy and not just the bug of rclone. More information about this is in description above. The Webdav server will check theDestinationheader in query uri if it match the internal server.
Edit2: Because of RFC2518,Destinationheader should be absolute URI. Convert it to relative is not the solution. The better way is to rewrite the scheme and path to match the internal communication when proxy to IIS backend.
Edit3:if-statein Nginx will double escape() the query string (like convert already converted string%6Eto%256E). So I usemap(should be placed inhttpsection) instead. For some reason,IIS Webdavserver behind proxy will check ifMOVEmethod’s query stringDestinationis match the proxyedHost. To avoid error, delete or comment theproxy_set_header host $http_hostinlocation /S4f_/ {..}. ↩︎Optional
Set basic auth for Nginx.Generate your
conf/userpass.txtfile by commandhtpasswdor onlineMD5 htpasswdgenerator. Text inside are likeshare:$apr1$AOW5qtOx$7D3rsVK/jIQuQYqd11/Xg0. Add those lines in yournginx.confserversection.nginx auth_basic "Auth"; auth_basic_user_file userpass.txt;↩︎
时空乱流记录