localhost 拒絕了我們的連線請求 vscode_移動辦公——在iPad上執行VSCode

今天也要開心呢發表於2020-12-12

導語


越來越多的業務開始上雲,現在的個人開發環境也有上雲的趨勢。利用VSCode的遠端開發模式,很多開發者已經把程式碼和編譯環境都配置在雲伺服器上,本地的筆記本只是一個接入端。而這個接入端不一定是電腦,也可以使iPad等更輕量的裝置。


Code Server


到目前為止,微軟還沒有提供iPad版本的VSCode。幸運的是,有一個叫coderom的團隊提供了一個code server,安裝到Linux伺服器上之後,可以通過瀏覽器開啟VSCode,進而實現從iPad上遠端開發的目的。
同時,coderom還提供了code server的docker映象,配置起來更加簡便。
所以你需要的資源包括:一臺雲伺服器,一個域名(可選,但用域名會方便很多),一臺配了鍵盤的iPad。


開箱即用的方式


雲服務安裝好docker之後,直接把code server的容器跑起來就可以了:

docker run -it -p 80:8080 
  -v "$PWD/code:/home/coder/project" 
  -u "$(id -u):$(id -g)" 
  -e PASSWORD='abde32c' 
  codercom/code-server:3.7.4

然後在iPad的瀏覽器上開啟你的域名

74107cc1424be62508b89340bc7d8990.png

上面docker啟動時時已經通過環境變數指定密碼,如果不指定密碼,code server會隨機生成一個。輸入密碼,即可開始iPad程式設計之旅。

a438405649ff1efd7bf9ac8007cd5454.png

安全保障


從上面的截圖右下角也可以看到,現在訪問code server沒有加密,很不安全。一個方式是用一個ssh客戶端進行埠轉發,然後iPad直接訪問本地埠來訪問code server。iPad的ssh客戶端可以選擇Terminus。於是,docker容器改成只監聽本地埠:

docker run -it -p 127.0.0.1:8090:8080 
  -v "$PWD/code:/home/coder/project" 
  -u "$(id -u):$(id -g)" 
  -e PASSWORD='abde32c' 
  codercom/code-server:3.7.4

然後用Terminus做埠轉發:

520cf5179d210dca035e56412036af8d.png

但是iPad一個應用不在前臺時很容易被清理掉,然後埠轉發就停止了。好在現在iPad可以多應用開啟,只不過要擠佔有限的螢幕空間,用起來並不暢快。

HTTPS


另一個方式是通過HTTPS訪問。code server也自帶了https選項,但這裡我沒有選擇使用它。因為我覺得更合適的方式是各個模組各司其職,讓code server專門負責vscode的事情,通過另一個程式來做HTTPS的請求轉發。另外一個考慮是,自己來做HTTPS請求轉發,可以靈活定製。可以考慮寫一個golang程式來做HTTPS監聽,並將請求轉發到本地的8090埠。

提供HTTPS服務需要有證書,因為這裡的HTTPS服務僅僅是自己訪問,只需要用一個自簽名證書就可以了。推薦的方式是先生成一個rootCA根證書,再用這個根證書頒發一個伺服器證書server.key和server.crt。這樣客戶端只需要信任根證書,服務端證書就自動被信任,而且以後服務端再換證書,客戶端也不用修改。注意下面的http://your.domain.com需要替換成你的真實域名。

# generate rootCA locally.
openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -days 750 
    -subj "/C=US/L=CA/O=VSCode Server CA/CN=VSCode Server Root CA" 
    -key rootCA.key -out rootCA.crt
    
# generate server certificate and key.
openssl genrsa -out server.key 2048
openssl req -new -sha256 
    -key server.key 
    -subj "/C=US/ST=CA/O=VSCode Server/CN=your.domain.com" 
    -out server.csr
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -days 365 -sha256 
    -extensions SAN 
    -extfile <(cat /etc/ssl/openssl.cnf 
        <(printf "n[SAN]nsubjectAltName = DNS:your.domain.comnextendedKeyUsage = serverAuth")) 
    -in server.csr -out server.crt

伺服器上需要rootCA.crt、server.key、server.crt,iPad需要rootCA.crt,rootCA.key應該僅儲存在你的個人機器上。然後再實現一個golang的HTTPS伺服器:

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httputil"
    "net/url"
    "time"
)

func main() {
    codeServer, _ := url.Parse("http://localhost:8090/")
    codeServerDirector := func(req *http.Request) {
        req.Header.Add("X-Forwarded-Host", req.Host)
        req.Header.Add("X-Origin-Host", codeServer.Host)
        req.URL.Scheme = "http"
        req.URL.Host = codeServer.Host
    }
    codeServerProxy := &httputil.ReverseProxy{Director: codeServerDirector}

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("proxy receive request from: %v at: %vn", r.RemoteAddr, time.Now())
        codeServerProxy.ServeHTTP(w, r)
    })

    certificate, err := tls.LoadX509KeyPair("../cert/server.crt", "../cert/server.key")
    if err != nil {
        fmt.Printf("load certificate fail: %vn", err)
        return
    }

    certPool := x509.NewCertPool()
    ca, err := ioutil.ReadFile("../cert/rootCA.crt")
    if err != nil {
        fmt.Printf("read ca file fail:%vn", err)
        return
    }
    if ok := certPool.AppendCertsFromPEM(ca); !ok {
        fmt.Println("failed to append certs")
        return
    }

    tlsconfig := &tls.Config{
        Certificates: []tls.Certificate{certificate},
        ClientAuth:   tls.NoClientCert, //tls.RequireAndVerifyClientCert,
        ClientCAs:    certPool,
    }

    s := &http.Server{
        Addr:      "0.0.0.0:443",
        Handler:   nil,
        TLSConfig: tlsconfig,
    }

    s.ListenAndServeTLS("", "")
}

這個golang的HTTPS伺服器的功能非常單一,就是將所有請求轉發給localhost:8090,即code server的埠。
iPad客戶端需要信任rootCA.crt,然後直接開啟https://your.domain.com,然後就可以到達之前輸入密碼的介面,進入程式設計環境。


還能更安全嗎


注意現在只是客戶端驗證服務端的證書,服務端並沒有驗證客戶端的證書,攔在攻擊者前面的只有一個密碼了。我們可以讓服務端也校驗客戶端的證書,實現雙向認證。先用之前的根證書頒發一個客戶端證書:

# generate client certificate and key.
openssl genrsa -out client.key 2048
openssl req -new -sha256 
    -key client.key 
    -subj "/C=US/ST=CA/O=VSCode Client/CN=your.domain.com" 
    -out client.csr
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -days 365 -sha256 
    -extensions SAN 
    -extfile <(cat /etc/ssl/openssl.cnf 
        <(printf "n[SAN]nsubjectAltName = DNS:your.domain.comnextendedKeyUsage = clientAuth")) 
    -in client.csr -out client.crt
openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt

然後整個打包傳送到iPad上,安裝為描述檔案。注意我們已經信任了根證書,這個client.crt會自動被信任。HTTPS伺服器的tls配置修改為要求客戶端提供證書:

tlsconfig := &tls.Config{
    Certificates: []tls.Certificate{certificate},
    ClientAuth:   tls.RequireAndVerifyClientCert,
    ClientCAs:    certPool,
}

接著重新啟動HTTPS伺服器。
這種方式在Mac的Chrome驗證通過,不過遺憾的是,iPad上的Chrome不認手動匯入的客戶端證書,Safari則僅在第一此連線時使用了客戶端證書,後面的又不讀取了,服務端會報tls: client didn't provide a certificate的錯誤。
所以目前暫時還只能使用單向HTTPS+密碼的方式,為了更加安全,埠就不能長期開啟了,僅在使用的時候把code server和HTTPS伺服器開啟。


總結


在iPad上用VSCode連線雲伺服器開發是可行的。通過配置獨立於cod server 的HTTPS轉發服務,可以加入更多的定製功能(比如自簽名證書和客戶端驗證)。期待最終能實現iPad上的雙向HTTPS驗證方式使用VSCode。

相關文章