春雨日記 about me tags

ホスト・クライアント共にHTTPプロキシ配下にある環境にて,RDP接続を行ってみました.

タイトルの文字数を気にするあまり意味不明な感じになっていますが,要はHTTPプロキシのCONNECTを通じた443/TCP以外の自由な通信が許されない環境にてRDPしてみたという話です.

プロキシ要素を取り除けばモバイル回線やCATVといったCGNAT下でもそのまま(RDPに限らず)応用可能なので,割と便利なんじゃないかと思います.

構成図

(スペルチェッカーが写ってるのは気にしない)

必要な物

  • 踏み台サーバ
  • RDPホスト
  • RDPクライアント
  • ドメイン

nginxのストリームプロキシを使ってfrpとwebsocketを同じポートで動かしています.

なので既存のウェブサーバがあればそのまま挿入することも可能

frp

https://github.com/fatedier/frp

frpはローカルのポートを踏み台経由で公開するソフトウェアで,サーバであるfrpsとクライアントであるfrpcから構成されます.

現在G-Clusterの実験でも使用しており,規制中のpovo回線でも圧倒的な安定感を誇ります.

Gost

https://github.com/ginuerzh/gost

Goで書かれた,トンネル技術を網羅したソフトウェアです.

これ1つで様々なトンネルやプロキシに対応できますが,今回は踏み台からRDPクライアントまでをWebSocketで繋いで貰います.

TCPからWebSocketに変換,逆変換が出来るので大変便利です.

今回の環境

場所 OS
踏み台サーバ Rocky Linux(SELinux無効)
RDPホスト Windows 10 Pro
RDPクライアント Windows 10 Pro

1. サーバ設定

では踏み台から.

frp

  • 実行ファイルの用意
1
2
3
wget https://github.com/fatedier/frp/releases/download/v0.44.0/frp_0.44.0_linux_amd64.tar.gz
tar xvf frp_0.44.0_linux_amd64.tar.gz
cd frp_0.44.0_linux_amd64
  • frps.iniの編集
1
nano frps.ini
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[common]
bind_port = 8880
tls_only = true
tls_enable = true
tls_cert_file = server.crt
tls_key_file = server.key
tls_trusted_ca_file = ca.crt
authentication_method = token
token = <トークン>
authenticate_heartbeats = true
authenticate_new_work_conns = true
allow_ports = 8881

*トークンは認証に利用するfrpcと共通の文字列です.適当に生成したbase64あたりで良いと思います.

*証明書は https://github.com/fatedier/frp#encryption-and-compression を見ながら作る

  • 実行
1
./frps -c ./frps.ini

Gost

  • 実行ファイルの用意
1
2
wget https://github.com/ginuerzh/gost/releases/download/v2.11.2/gost-linux-amd64-2.11.2.gz
gzip -d gost-linux-amd64-2.11.2.gz
  • 実行
1
./gost-linux-amd64-2.11.2 -L "ws://127.0.0.1:8883/127.0.0.1:8881?path=/ws&compression=true"

nginx

  • ドメインの用意

サブドメインでいいので用意しとくと楽です.

Let’s Encryptあたりでドメインの証明書を用意して下さい.

  • CA/証明書の用意

セキュリティのため,TLSクライアント認証を利用します.

https://qiita.com/tarosaiba/items/9fa3320b633e0f5e87b5

あたりを参考にしながら/etc/nginx/rdp-ca/に用意して下さい.

  • 設定
1
nano /etc/nginx/nginx.conf
stream {
    upstream frp {
        server 127.0.0.1:8882;
    }

    upstream web {
        server 127.0.0.1:8443;
    }

    map $ssl_preread_protocol $upstream {
        default frp;
        "TLSv1.3" web;
        "TLSv1.2" web;
        "TLSv1.1" web;
        "TLSv1.0" web;
    }

    server {
        listen 443;
        proxy_pass $upstream;
        ssl_preread on;
        proxy_protocol on;
    }

    server {
        listen 127.0.0.1:8882 proxy_protocol;
        proxy_pass 127.0.0.1:8880;
    }
}
1
nano /etc/nginx/conf.d/rdp.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

upstream websocket {
    server 127.0.0.1:8883;
}

server {
    listen 80;
    listen [::]:80;
    server_name <ドメイン>;
    if ($request_uri !~ ^/.well-known/) {
      return 301 https://$host$request_uri;
    }
}

server {
    listen 127.0.0.1:8443 ssl http2 proxy_protocol;
    server_name <ドメイン>;

    real_ip_header proxy_protocol;
    set_real_ip_from 127.0.0.1;

    ssl_certificate         /etc/letsencrypt/live/<ドメイン>/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/<ドメイン>/privkey.pem;

    ssl_verify_client on;
    ssl_client_certificate /etc/nginx/rdp-ca/ca.crt;

    location / {
        return 401;
    }

    location /ws {
          proxy_http_version 1.1;
          proxy_pass http://websocket;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
          proxy_set_header Host $host;
          proxy_read_timeout 61s;
          proxy_buffering off;
    }
}
  • nginx 再起動
1
systemctl restart nginx

2. RDPホスト設定

ホストでは転送のためにfrpcを利用します.

https://github.com/fatedier/frp/releases/tag/v0.44.0 から適切なバイナリを用意して展開します.

SSHあたりで証明書を持ってきて,frpc.iniをこんな感じに

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[common]
server_addr = 踏み台のアドレス(ドメインでも可)
server_port = 443
http_proxy = http://プロキシ
tls_enable = true
tls_cert_file = client.crt
tls_key_file = client.key
tls_trusted_ca_file = ca.crt
token = <トークン>
authenticate_heartbeats = true
authenticate_new_work_conns = true

[rdp]
type = tcp
local_ip = 127.0.0.1
bind_addr = 127.0.0.1
local_port = 3389
remote_port = 8881

後は普通に

1
.\frps.exe -c .\frps.ini

で起動すればOK

3. RDPクライアント設定

クライアントではWebSocketを使用して接続します.

https://github.com/ginuerzh/gost/releases/tag/v2.11.2 から適当なバイナリをダウンロードして展開

なぜかWindows版がウイルス判定を食らいます.心配なら自分でビルドすればOK

nginxのクライアント証明書を同じディレクトリにコピーし,

1
.\gost-linux-amd64-2.11.2 -L=tcp://127.0.0.1:12345 -F http://<プロキシ> -F="forward+wss://<WebSocketのドメイン>:443?path=/ws&cert=user1.crt&key=user1.key"

SNIが重要なのでちゃんとドメインで指定して下さい.

繋ぐ

Windowsの内蔵RDPクライアントで127.0.0.1:12345に接続するとリモートサーバに繋がります. さすがRDPだけあって,コピペできるしレスポンスもまあまあ良い感じ.

実験用プロキシサーバのログでも,きちんと443のみで通信している事が確認できます.

おわりに

無事,踏み台を利用する事でプロキシ環境下でもRDPする事ができました.

使用している主要ソフトウェアが2つとも中国で開発されているという所に,この分野では圧倒的な差をつけられている事を実感させられますね…

ちなみに,RDPをVNCに,GostをnoVNCに置き換えることでブラウザを使ったリモートVNCアクセスが可能になりますが,あまりにレスポンスがアレだった為ボツになりました.

⚠ファイアウォールは絶対有効にして下さい.⚠

一応bindが127.0.0.1のみとなっていますが,サーバには暗号化されていないポートが転送され,見えている状態です.

暗号化された通信以外を許可しないで下さい.