4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SSHできない環境でもブラウザで画面転送: OCI ComputeにnoVNC+Nginxを構築

4
Posted at

SSHできない環境でもブラウザで画面転送: OCI ComputeにnoVNC+Nginxを構築

目的

SSH接続が許可されない環境でも、サーバの画面転送が必要になることがあります。
そのようなケースに備えて、OCI(Oracle Cloud Infrastructure)のCompute上にVNC環境を用意し、noVNCを使ってブラウザからHTTPSで操作できる構成を作ります。

最終的には、利用者はブラウザで次のURLへアクセスするだけです。

https://<COMPUTE_PUBLIC_IP_OR_FQDN>/vnc.html

SSHトンネルは使いません。外部に公開するポートはHTTPSの 443 のみです。

全体構成

Browser
  -> HTTPS :443
  -> Nginx
  -> noVNC/websockify 127.0.0.1:6080
  -> VNC 127.0.0.1:5901
  -> Desktop session

ポイントは、60805901 を外部公開しないことです。
外部からはNginxの 443 だけを開け、Nginxが内部のnoVNCへリバースプロキシします。

用途 ポート 外部公開
HTTPS入口 443 する
noVNC/websockify 6080 しない
VNC 5901 しない

前提

  • OCI ComputeにSSHログインできること
  • Compute上で sudo が使えること
  • ComputeからOSリポジトリ、GitHub、PyPIへ到達できること
  • OCI Security ListまたはNSGで TCP/443 を許可できること

外部通信できないComputeの場合、dnfgit clonepip install が失敗します。その場合はNAT Gateway、またはオフライン転送などを使います。

1. noVNCセットアップスクリプトを配置

管理端末からComputeへスクリプトを転送します。

scp /path/to/setup-novnc-server.sh \
  <SSH_USER>@<COMPUTE_PUBLIC_IP_OR_FQDN>:~/novnc-setup/setup-novnc-server.sh

Compute側で実行します。

mkdir -p ~/novnc-setup
cd ~/novnc-setup
chmod +x setup-novnc-server.sh
./setup-novnc-server.sh

途中でVNCパスワードを聞かれます。これはLinuxログインパスワードではなく、noVNC画面で入力するVNC専用パスワードです。

Password:
Verify:
Would you like to enter a view-only password (y/n)?

操作用に使う場合、view-only passwordは通常 n でOKです。

2. setup-novnc-server.sh

以下がサーバ側セットアップスクリプトです。

#!/usr/bin/env bash
set -euo pipefail

# Server-side setup for noVNC on a compute instance.
# noVNC and VNC are bound to 127.0.0.1 so they can be exposed safely
# through a local reverse proxy such as Nginx on HTTPS/443.

VNC_DISPLAY="${VNC_DISPLAY:-1}"
VNC_GEOMETRY="${VNC_GEOMETRY:-1440x900}"
VNC_DEPTH="${VNC_DEPTH:-24}"
NOVNC_PORT="${NOVNC_PORT:-6080}"
VNC_PORT=$((5900 + VNC_DISPLAY))
USER_HOME="$(cd ~ && pwd)"

log() {
  printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}

need_cmd() {
  command -v "$1" >/dev/null 2>&1
}

install_packages() {
  log "Installing desktop, VNC, and Python prerequisites"

  if need_cmd dnf; then
    sudo dnf install -y \
      tigervnc-server \
      tigervnc-server-module \
      python3 \
      python3-pip \
      git \
      xterm \
      dbus-x11 \
      xorg-x11-xauth \
      xorg-x11-server-Xvfb || {
        log "Package install failed. If this instance has no outbound repo access, attach a NAT gateway, mirror repo, OS Management/OSMH source, or install RPMs offline."
        exit 1
      }

    if ! rpm -q xfce4-session >/dev/null 2>&1; then
      sudo dnf install -y @xfce-desktop-environment || true
    fi
  elif need_cmd yum; then
    sudo yum install -y \
      tigervnc-server \
      python3 \
      python3-pip \
      git \
      xterm \
      dbus-x11 \
      xorg-x11-xauth || {
        log "Package install failed. If this instance has no outbound repo access, attach a NAT gateway, mirror repo, OS Management/OSMH source, or install RPMs offline."
        exit 1
      }

    sudo yum groupinstall -y "Xfce" || true
  elif need_cmd apt-get; then
    sudo apt-get update
    sudo apt-get install -y \
      tigervnc-standalone-server \
      tigervnc-common \
      python3 \
      python3-pip \
      git \
      xfce4 \
      xfce4-goodies \
      xterm \
      dbus-x11
  else
    log "No supported package manager found. Install TigerVNC, noVNC, websockify, and a desktop environment manually."
    exit 1
  fi
}

install_websockify_if_needed() {
  if need_cmd websockify || python3 -c 'import websockify' >/dev/null 2>&1; then
    return 0
  fi

  log "Installing websockify with pip"
  python3 -m pip install --user websockify || {
    log "Could not install websockify with pip. If this instance has no outbound access, install the websockify Python package offline."
    exit 1
  }

  export PATH="${USER_HOME}/.local/bin:${PATH}"
}

install_novnc_if_needed() {
  if find_novnc_proxy >/dev/null 2>&1; then
    return 0
  fi

  if [ -f "${USER_HOME}/noVNC/utils/novnc_proxy" ] || [ -f "${USER_HOME}/noVNC/utils/novnc_proxy.py" ]; then
    return 0
  fi

  log "Installing noVNC under ${USER_HOME}/noVNC"
  git clone --depth 1 https://github.com/novnc/noVNC.git "${USER_HOME}/noVNC" || {
    log "Could not clone noVNC. If this instance has no outbound access, copy a noVNC checkout to ${USER_HOME}/noVNC from your local machine."
    exit 1
  }
}

configure_vnc() {
  log "Configuring VNC for display :${VNC_DISPLAY}"
  mkdir -p "${USER_HOME}/.vnc"

  if [ ! -f "${USER_HOME}/.vnc/passwd" ]; then
    printf 'Set a VNC password. This protects the desktop session behind noVNC.\n'
    vncpasswd
  fi

  cat >"${USER_HOME}/.vnc/xstartup" <<'EOF'
#!/bin/sh
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS

if command -v startxfce4 >/dev/null 2>&1; then
  exec startxfce4
fi

if command -v gnome-session >/dev/null 2>&1; then
  exec gnome-session
fi

exec xterm
EOF
  chmod +x "${USER_HOME}/.vnc/xstartup"
}

start_vnc() {
  log "Starting TigerVNC on 127.0.0.1:${VNC_PORT}"
  vncserver -kill ":${VNC_DISPLAY}" >/dev/null 2>&1 || true
  vncserver ":${VNC_DISPLAY}" \
    -localhost yes \
    -geometry "${VNC_GEOMETRY}" \
    -depth "${VNC_DEPTH}"
}

find_novnc_proxy() {
  for candidate in \
    "${USER_HOME}/noVNC/utils/novnc_proxy" \
    "${USER_HOME}/noVNC/utils/novnc_proxy.py" \
    /usr/share/novnc/utils/novnc_proxy \
    /usr/share/novnc/utils/novnc_proxy.py \
    /usr/share/noVNC/utils/novnc_proxy \
    /usr/share/noVNC/utils/novnc_proxy.py; do
    if [ -x "$candidate" ] || [ -f "$candidate" ]; then
      printf '%s\n' "$candidate"
      return 0
    fi
  done
  return 1
}

start_novnc() {
  local proxy
  proxy="$(find_novnc_proxy)" || {
    log "Could not find novnc_proxy. Check the noVNC package installation."
    exit 1
  }

  log "Starting noVNC on 127.0.0.1:${NOVNC_PORT}, forwarding to 127.0.0.1:${VNC_PORT}"
  pkill -f "novnc_proxy.*${NOVNC_PORT}" >/dev/null 2>&1 || true
  nohup "$proxy" \
    --listen "127.0.0.1:${NOVNC_PORT}" \
    --vnc "127.0.0.1:${VNC_PORT}" \
    >"${USER_HOME}/novnc-${NOVNC_PORT}.log" 2>&1 &

  sleep 2
  if ! pgrep -f "novnc_proxy.*${NOVNC_PORT}" >/dev/null 2>&1; then
    log "noVNC did not stay running. See ${USER_HOME}/novnc-${NOVNC_PORT}.log"
    exit 1
  fi
}

print_next_steps() {
  cat <<EOF

Server setup completed.

noVNC is listening locally on this compute instance:

  http://127.0.0.1:${NOVNC_PORT}/vnc.html

VNC is listening locally on:

  127.0.0.1:${VNC_PORT}

For HTTPS browser access, put Nginx or another reverse proxy in front of
127.0.0.1:${NOVNC_PORT} and expose only TCP/443 externally.

Useful server checks:

  vncserver -list
  pgrep -af 'novnc_proxy|websockify|Xtigervnc'
  tail -n 100 ~/novnc-${NOVNC_PORT}.log

EOF
}

install_packages
install_websockify_if_needed
install_novnc_if_needed
configure_vnc
start_vnc
start_novnc
print_next_steps

3. 起動確認

vncserver -list
pgrep -af 'novnc_proxy|websockify|Xtigervnc'
tail -n 20 ~/novnc-6080.log

noVNCは 127.0.0.1:6080、VNCは 127.0.0.1:5901 で待ち受けていればOKです。

4. OCIで443を開ける

OCI Consoleで対象ComputeのSecurity ListまたはNSGに、HTTPS用のingress ruleを追加します。

image.png

推奨は接続元IPを絞ることです。

Source Type: CIDR
Source CIDR: <YOUR_GLOBAL_IP>/32
IP Protocol: TCP
Destination Port Range: 443

検証だけなら 0.0.0.0/0 でも動きますが、本番運用では避けます。

5. NginxでHTTPS化する

Nginx、Basic認証用の htpasswd、OpenSSLを入れます。

sudo dnf install -y nginx httpd-tools openssl

Basic認証ユーザーを作ります。

sudo htpasswd -c /etc/nginx/.novnc_htpasswd novncuser

検証用の自己署名証明書を作ります。

sudo mkdir -p /etc/nginx/tls
sudo openssl req -x509 -nodes -days 365 \
  -newkey rsa:2048 \
  -keyout /etc/nginx/tls/novnc.key \
  -out /etc/nginx/tls/novnc.crt \
  -subj "/CN=<COMPUTE_PUBLIC_IP_OR_FQDN>"

Nginxのリバースプロキシ設定を作ります。

sudo tee /etc/nginx/conf.d/novnc.conf >/dev/null <<'EOF'
server {
    listen 443 ssl;
    server_name _;

    ssl_certificate     /etc/nginx/tls/novnc.crt;
    ssl_certificate_key /etc/nginx/tls/novnc.key;

    auth_basic "noVNC";
    auth_basic_user_file /etc/nginx/.novnc_htpasswd;

    location / {
        proxy_pass http://127.0.0.1:6080;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;

        proxy_read_timeout 3600;
        proxy_send_timeout 3600;
    }
}
EOF

SELinuxでproxyが止まる場合に備えて、Nginxからローカルポートへ接続できるようにします。

sudo setsebool -P httpd_can_network_connect 1

firewalldを使っている場合はHTTPSを開けます。

sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

Nginxを起動します。

sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl restart nginx
sudo ss -lntp | grep ':443'

次のように nginx0.0.0.0:443 で待ち受けていればOKです。

LISTEN ... 0.0.0.0:443 ... users:(("nginx",...))

6. ブラウザから接続

ブラウザで開きます。

https://<COMPUTE_PUBLIC_IP_OR_FQDN>/vnc.html

流れは次の通りです。

  1. 自己署名証明書の警告を許可
  2. Nginx Basic認証を入力
  3. noVNC画面でVNCパスワードを入力
  4. GUIデスクトップが表示される

トラブルシューティング

ERR_CONNECTION_REFUSED

Compute側でNginxが 443 をlistenしていない可能性があります。

sudo nginx -T | grep -n "conf.d\|listen 443\|novnc"
sudo systemctl restart nginx
sudo ss -lntp | grep ':443'

ERR_TIMED_OUT

OCI Security List / NSG、またはOS firewallで 443 が通っていない可能性があります。

502 Bad Gateway

NginxからnoVNCへ接続できていません。

pgrep -af 'novnc_proxy|websockify'
tail -n 50 ~/novnc-6080.log

まとめ

OCI Compute上に TigerVNC + noVNC + Nginx を構成すると、SSHトンネルなしでブラウザからHTTPSのみの画面転送環境を作れます。

外部公開は 443 だけにし、60805901127.0.0.1 に閉じるのがポイントです。
検証では自己署名証明書でも動作しますが、本番運用では正式なTLS証明書、接続元IP制限、強力な認証方式を組み合わせることを推奨します。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?