Authenticated Web VSCode Server

Authenticated web vscode server with Pritunl Zero and Coder Server

This script will create an authenticated Visual Studio code web server with Pritunl Zero allowing for secure access to a vscode editor running in the browser. It can be used on any RHEL9 distribution such as AlmaLinux 9 or Oracle Linux 9 . For other distributions the commands will need to be adjusted. The Coder VS Code Server provides a server to run vscode in the web browser. This script will run this server on the localhost then install and configure Pritunl Zero to provided secure and authenticated access to the web vscode. Additional users can be added to provide a self hosted vscode server for collaborating or sharing code between systems.

First create a server with either a public IPv4 or IPv6 address. If the server is only running on a private network refer to the next section on how to create the Lets Encrypt certificate using DNS CNAME verification. This script will use HTTP verification on port 80 on both the domains configured below.

Before starting the installation configure two domains the ZERO_DOMAIN for the Pritunl Zero administrator console and the CODER_DOMAIN for the web vscode domain. Both of these domains should have A or AAAA records for the IPv4 and IPv6 address of the Pritunl Zero server. The ROOT_DOMAIN is used if WebAuthn tokens are configured, this domain controls the scope of those token validations, it's best to use the highest level domain to allow the tokens to function across multiple sub-domains. A DNS record does not need to be created or modified for the ROOT_DOMAIN.

This script will store all data in the persistent paths /var/lib/mongo and /var/lib/coder. These are the only two paths that need to be maintained when re-initializing the system.

Once the script is completed it will display the default password. This can then be used to login to the admin domain or vscode domain. Open the /logout path in the vscode domain from the browser URL input to logout.

#!/bin/bash
# persistent paths: /var/lib/mongo /var/lib/coder
set -e 

ROOT_DOMAIN="pritunl.demo"
ZERO_DOMAIN="zero.pritunl.demo"
CODER_DOMAIN="coder.pritunl.demo"

get_node_id() {
  if [ $# -eq 0 ]; then
    echo "Error: File path is required" >&2
    echo "Usage: get_node_id <node_id_file_path>" >&2
    return 1
  fi
  local node_id_file="$1"
  local node_id
  sudo mkdir -p "$(dirname "$node_id_file")"
  if [ -f "$node_id_file" ]; then
    node_id=$(cat "$node_id_file")
  else
    node_id=$(echo -n $(printf "%08x" $(date +%s))$(hexdump -n 8 -e '4/4 "%08x" 1 "\n"' /dev/urandom))
    echo "$node_id" | sudo tee "$node_id_file" > /dev/null
    sudo chmod 444 "$node_id_file"
  fi
  echo "$node_id"
}

sudo dnf -y update

sudo setenforce 0
sudo sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
sudo systemctl disable --now firewalld.service

sudo dnf -y install dnf-automatic
sudo sed -i 's/^upgrade_type =.*/upgrade_type = default/g' /etc/dnf/automatic.conf
sudo sed -i 's/^download_updates =.*/download_updates = yes/g' /etc/dnf/automatic.conf
sudo sed -i 's/^apply_updates =.*/apply_updates = yes/g' /etc/dnf/automatic.conf
sudo systemctl enable --now dnf-automatic.timer

sudo tee /etc/yum.repos.d/pritunl.repo << EOF
[pritunl]
name=Pritunl Repository
baseurl=https://repo.pritunl.com/stable/yum/oraclelinux/9/
gpgcheck=1
enabled=1
gpgkey=https://raw.githubusercontent.com/pritunl/pgp/master/pritunl_repo_pub.asc
EOF

sudo tee /etc/yum.repos.d/mongodb-org.repo << EOF
[mongodb-org]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/8.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-8.0.asc
EOF

sudo dnf -y install pritunl-zero mongodb-org

sudo systemctl enable --now mongod

NODE_ID=$(get_node_id "/var/lib/mongo/node_id")

sudo tee /etc/pritunl-zero.json << EOF
{
    "mongo_uri": "mongodb://localhost:27017/pritunl-zero",
    "node_id": "$NODE_ID"
}
EOF
sudo chmod 600 /etc/pritunl-zero.json

sudo dnf -y group install -y "Development Tools"
sudo dnf -y module install nodejs:20

sudo useradd -r -s /sbin/nologin coder
sudo mkdir -p /home/coder
sudo chown coder:coder /home/coder
sudo chmod 700 /home/coder

sudo mkdir -p /var/lib/coder
sudo chown coder:coder /var/lib/coder
sudo chmod 700 /var/lib/coder
sudo mkdir -p /var/lib/coder/local
sudo chown coder:coder /var/lib/coder/local
sudo mkdir -p /var/lib/coder/config
sudo chown coder:coder /var/lib/coder/config
sudo mkdir -p /var/lib/coder/data
sudo chown coder:coder /var/lib/coder/data

sudo ln -snf /var/lib/coder/local /home/coder/.local
sudo chown coder:coder /home/coder/.local
sudo ln -snf /var/lib/coder/config /home/coder/.config
sudo chown coder:coder /home/coder/.config
sudo ln -snf /var/lib/coder/data /home/coder/data
sudo chown coder:coder /home/coder/data

sudo mkdir -p /home/coder/.config/code-server
sudo chown coder:coder /home/coder/.config/code-server
sudo tee /home/coder/.config/code-server/config.yaml << EOF
bind-addr: 127.0.0.1:8000
auth: none
cert: false
EOF
sudo chown coder:coder /home/coder/.config/code-server/config.yaml

sudo -u coder bash -c "cd /home/coder; npm install code-server"

sudo tee /etc/systemd/system/coder.service << EOF
[Service]
User=coder
Group=coder
WorkingDirectory=/home/coder
ExecStart=/home/coder/node_modules/.bin/code-server
TimeoutStopSec=5s
LimitNOFILE=500000
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
ProtectHostname=true
ProtectKernelTunables=true

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now pritunl-zero
sudo systemctl enable --now coder

sudo pritunl-zero upsert service --name=coder --type=http --role=coder --domain="$CODER_DOMAIN" --server="http://127.0.0.1:8000" --share-session=true --websockets=true --logout-path="/logout"
sudo pritunl-zero upsert certificate --name=pritunl-cert --type=lets_encrypt --acme-domain=$ZERO_DOMAIN --acme-domain=$CODER_DOMAIN --acme-type=http
sudo pritunl-zero upsert node --name=self --mangement=true --proxy=true --management-domain=$ZERO_DOMAIN --webauthn-domain=$ROOT_DOMAIN --add-certificate=pritunl-cert --add-service=coder
sudo pritunl-zero upsert policy --name=pritunl-zero --role=coder --add-service=coder
sudo pritunl-zero upsert user --name=pritunl --role=coder
sudo pritunl-zero default-password

DNS CNAME Verification

Lets Encrypt DNS CNAME verification can be used if the server is going to run on a private network or is configured so that the Lets Encrypt servers are unable to access port 80 on the Pritunl Zero server. To do this replace the last section of the script above with the commands below. This example uses Cloudflare but AWS Router 53 and Oracle Cloud DNS are also supported. Set CLOUDFLARE_SECRET to the Cloudflare token to access the DNS API for that domain zone. This will allow the Pritunl Zero server to create CNAME records to complete the Lets Encrypt certificate verification.

CLOUDFLARE_SECRET="TOKEN"

sudo pritunl-zero upsert service --name=coder --type=http --role=coder --domain="$CODER_DOMAIN" --server="http://127.0.0.1:8000" --share-session=true --websockets=true --logout-path="/logout"
sudo pritunl-zero upsert secret --name=pritunl-dns --type=cloudflare --cloudflare-token=$CLOUDFLARE_SECRET
sudo pritunl-zero upsert certificate --name=pritunl-cert --type=lets_encrypt --acme-domain=$ZERO_DOMAIN --acme-domain=$CODER_DOMAIN --acme-type=dns --acme-api=cloudflare --acme-secret=pritunl-dns
sudo pritunl-zero upsert node --name=self --mangement=true --proxy=true --management-domain=$ZERO_DOMAIN --webauthn-domain=$ROOT_DOMAIN --add-certificate=pritunl-cert --add-service=coder
sudo pritunl-zero upsert policy --name=pritunl-zero --role=coder --add-service=coder
sudo pritunl-zero upsert user --name=pritunl --role=coder
sudo pritunl-zero default-password