Guacamole Host Setup

From HyperSecurity Wiki
Jump to: navigation, search

This script setups and configures guacamole docker containers, lets encrypt, reverse nginx proxy.

#!/usr/bin/env bash                                                                                                                                                                           
set -euo pipefail                                                                                                                                                                             
                                                                                                                                                                                              
### --- Prompt Section ---                                                                                                                                                                    
read -rp "Enter your domain (FQDN): " DOMAIN                                                                                                                                                  
read -rp "Enter email for Let's Encrypt: " EMAIL                                                                                                                                              
read -rp "Enter the VNC username to create and configure: " VNC_USER                                                                                                                          
                                                                                                                                                                                              
# Generate random passwords                                                                                                                                                                   
ADMIN_PASS="$(openssl rand -base64 12)"                                                                                                                                                       
DB_ROOT_PASS="$(openssl rand -base64 24 | tr -d '=+/')"                                                                                                                                       
DB_GUAC_PASS="$(openssl rand -base64 24 | tr -d '=+/')"                                                                                                                                       
                                                                                                                                                                                              
### --- Install dependencies (Debian/Ubuntu) ---                                                                                                                                              
echo "[INFO] Installing required packages..."                                                                                                                                                 
apt update                                                                                                                                                                                    
apt install -y docker.io docker-compose-plugin nginx certbot python3-certbot-nginx ufw \                                                                                                      
               curl whois net-tools xfce4 xfce4-goodies tightvncserver unzip                                                                                                                  
                                                                                                                                                                                              
# Decide compose command                                                                                                                                                                      
if command -v docker-compose >/dev/null 2>&1; then                                                                                                                                            
  COMPOSE="docker-compose"                                                                                                                                                                    
else                                                                                                                                                                                          
  COMPOSE="docker compose"                                                                                                                                                                    
fi                                                                                                                                                                                            
                                                                                                                                                                                              
### --- Layout ---                                                                                                                                                                            
APPDIR=/opt/guacamole                                                                                                                                                                         
NGXCONF=/etc/nginx/sites-enabled                                                                                                                                                              
WEBROOT=/var/www/html                                                                                                                                                                         
ENVFILE="$APPDIR/.env"                                                                                                                                                                        
COMPOSEFILE="$APPDIR/docker-compose.yml"                                                                                                                                                      
INITDIR="$APPDIR/initdb"                                                                                                                                                                      
GUAC_HOME="$APPDIR/guac-home"                                                                                                                                                                 
                                                                                                                                                                                              
mkdir -p "$APPDIR" "$INITDIR" "$GUAC_HOME/extensions" "$GUAC_HOME/lib"                                                                                                                        
                                                                                                                                                                                              
### --- .env with secrets ---                                                                                                                                                                 
install -m 0600 -o root -g root /dev/null "$ENVFILE"                                                                                                                                          
cat > "$ENVFILE" <<EOF                                                                                                                                                                        
GUAC_VERSION=1.5.5                                                                                                                                                                            
                                                                                                                                                                                              
# MariaDB                                                                                                                                                                                     
MYSQL_DATABASE=guacamole_db                                                                                                                                                                   
MYSQL_USER=guacamole_user                                                                                                                                                                     
MYSQL_PASSWORD=$DB_GUAC_PASS                                                                                                                                                                  
MYSQL_ROOT_PASSWORD=$DB_ROOT_PASS                                                                                                                                                             
                                                                                                                                                                                              
# Paths                                                                                                                                                                                       
APPDIR=$APPDIR                                                                                                                                                                                
GUAC_HOME=$GUAC_HOME                                                                                                                                                                          
EOF                                                                                                                                                                                           
chmod 600 "$ENVFILE"                                                                                                                                                                          
                                                                                                                                                                                              
### --- Docker network ---                                                                                                                                                                    
docker network create guac-network >/dev/null 2>&1 || true                                                                                                                                    
                                                                                                                                                                                              
### --- docker-compose.yml ---                                                                                                                                                                
cat > "$COMPOSEFILE" <<'YAML'                                                                                                                                                                 
services:                                                                                                                                                                                     
  mariadb:                                                                                                                                                                                    
    image: mariadb:10.11                                                                                                                                                                      
    restart: unless-stopped                                                                                                                                                                   
    environment:                                                                                                                                                                              
      MYSQL_DATABASE: ${MYSQL_DATABASE}                                                                                                                                                       
      MYSQL_USER: ${MYSQL_USER}                                                                                                                                                               
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}                                                                                                                                                       
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}                                                                                                                                             
    volumes:                                                                                                                                                                                  
      - ${APPDIR}/mariadb:/var/lib/mysql                                                                                                                                                      
      - ${APPDIR}/initdb:/docker-entrypoint-initdb.d:ro                                                                                                                                       
    healthcheck:                                                                                                                                                                              
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u${MYSQL_USER} -p${MYSQL_PASSWORD} || exit 1"]                                                                                       
      interval: 10s                                                                                                                                                                           
      timeout: 5s                                                                                                                                                                             
      retries: 20                                                                                                                                                                             
    networks: [guac-network]                                                                                                                                                                  
                                                                                                                                                                                              
  guacd:                                                                                                                                                                                      
    image: guacamole/guacd:${GUAC_VERSION}                                                                                                                                                    
    restart: unless-stopped                                                                                                                                                                   
    healthcheck:                                                                                                                                                                              
      test: ["CMD-SHELL", "nc -z 127.0.0.1 4822 || exit 1"]                                                                                                                                   
      interval: 10s                                                                                                                                                                           
      timeout: 5s                                                                                                                                                                             
      retries: 20                                                                                                                                                                             
    networks: [guac-network]                                                                                                                                                                  
                                                                                                                                                                                              
  guacamole:                                                                                                                                                                                  
    image: guacamole/guacamole:${GUAC_VERSION}                                                                                                                                                
    restart: unless-stopped                                                                                                                                                                   
    depends_on:                                                                                                                                                                               
      mariadb:                                                                                                                                                                                
        condition: service_healthy                                                                                                                                                            
      guacd:                                                                                                                                                                                  
        condition: service_healthy                                                                                                                                                            
    environment:                                                                                                                                                                              
      GUACD_HOSTNAME: guacd                                                                                                                                                                   
      MYSQL_HOSTNAME: mariadb                                                                                                                                                                 
      MYSQL_DATABASE: ${MYSQL_DATABASE}                                                                                                                                                       
      MYSQL_USER: ${MYSQL_USER}                                                                                                                                                               
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}                                                                                                                                                       
      GUACAMOLE_HOME: /guac-home                                                                                                                                                              
    volumes:                                                                                                                                                                                  
      - ${GUAC_HOME}:/guac-home                                                                                                                                                               
    healthcheck:                                                                                                                                                                              
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/guacamole/ || exit 1"]                                                                                                             
      interval: 15s                                                                                                                                                                           
      timeout: 5s                                                                                                                                                                             
      retries: 20                                                                                                                                                                             
    networks: [guac-network]                                                                                                                                                                  
                                                                                                                                                                                              
networks:                                                                                                                                                                                     
  guac-network:                                                                                                                                                                               
    external: true                                                                                                                                                                            
YAML                                                                                                                                                                                          
                                                                                                                                                                                              
### --- Initialize DB schema & JDBC extension ---                                                                                                                                             
echo "[INFO] Fetching JDBC auth package..."                                                                                                                                                   
cd "$APPDIR"                                                                                                                                                                                  
curl -fsSLo guacamole-auth-jdbc.tgz \                                                                                                                                                         
  https://downloads.apache.org/guacamole/${GUAC_VERSION}/binary/guacamole-auth-jdbc-${GUAC_VERSION}.tar.gz                                                                                    
tar -xzf guacamole-auth-jdbc.tgz                                                                                                                                                              
cp -f guacamole-auth-jdbc-${GUAC_VERSION}/mysql/*.sql "$INITDIR/"                                                                                                                             
cp -f guacamole-auth-jdbc-${GUAC_VERSION}/mysql/guacamole-auth-jdbc-mysql-${GUAC_VERSION}.jar "$GUAC_HOME/extensions/"                                                                        
                                                                                                                                                                                              
### --- Bring up DB & guacd so initdb runs ---                                                                                                                                                
$COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" up -d mariadb guacd                                                                                                                          
echo "[INFO] Waiting for MariaDB to be healthy..."                                                                                                                                            
$COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" ps                                                                                                                                           
                                                                                                                                                                                              
# Start guacamole after DB is healthy                                                                                                                                                         
$COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" up -d guacamole                                                                                                                              
                                                                                                                                                                                              
### --- Set guacadmin password in DB (salt + SHA-256) ---                                                                                                                                     
echo "[INFO] Setting guacadmin password in DB..."                                                                                                                                             
SALT="$(openssl rand -hex 16)"                                                                                                                                                                
HASH="$(printf '%s%s' "$ADMIN_PASS" "$SALT" | openssl dgst -sha256 -binary | od -An -t x1 | tr -d ' \n')"                                                                                     
                                                                                                                                                                                              
docker exec -i "$($COMPOSE --env-file "$ENVFILE" -f "$COMPOSEFILE" ps -q mariadb)" \                                                                                                          
  mysql -uroot -p"$DB_ROOT_PASS" guacamole_db <<SQL                                                                                                                                           
UPDATE guacamole_user                                                                                                                                                                         
   SET password_salt = UNHEX('$SALT'),                                                                                                                                                        
       password_hash = UNHEX('$HASH'),                                                                                                                                                        
       disabled = 0                                                                                                                                                                           
 WHERE username='guacadmin';                                                                                                                                                                  
SQL                                                                                                                                                                                           
                                                                                                                                                                                              
### --- Nginx ACME (HTTP) site for webroot ---                                                                                                                                                
echo "[INFO] Preparing Nginx HTTP challenge site..."                                                                                                                                          
mkdir -p "$WEBROOT/.well-known/acme-challenge"                                                                                                                                                
                                                                                                                                                                                              
cat > "$NGXCONF/guac-acme.conf" <<EOF                                                                                                                                                         
server {                                                                                                                                                                                      
    listen 80;                                                                                                                                                                                
    server_name $DOMAIN;                                                                                                                                                                      
                                                                                                                                                                                              
    location /.well-known/acme-challenge/ {                                                                                                                                                   
        root $WEBROOT;                                                                                                                                                                        
        try_files \$uri =404;                                                                                                                                                                 
    }                                                                                                                                                                                         
                                                                                                                                                                                              
    location / {                                                                                                                                                                              
        return 301 https://\$host\$request_uri;                                                                                                                                               
    }                                                                                                                                                                                         
}                                                                                                                                                                                             
EOF                                                                                                                                                                                           
                                                                                                                                                                                              
nginx -t && systemctl reload nginx                                                                                                                                                            
                                                                                                                                                                                              
### --- Obtain cert ---                                                                                                                                                                       
echo "[INFO] Requesting Let's Encrypt certificate..."                                                                                                                                         
certbot certonly --webroot -w "$WEBROOT" -d "$DOMAIN" \                                                                                                                                       
  --agree-tos -m "$EMAIL" --no-eff-email --non-interactive                                                                                                                                    
                                                                                                                                                                                              
### --- Nginx TLS reverse proxy (WebSockets, HSTS) ---                                                                                                                                        
echo "[INFO] Writing Nginx TLS proxy..."                                                                                                                                                      
cat > "$NGXCONF/guacamole.conf" <<'EOF'                                                                                                                                                       
map $http_upgrade $connection_upgrade { default upgrade; '' close; }                                                                                                                          
                                                                                                                                                                                              
server {                                                                                                                                                                                      
    listen 443 ssl http2;                                                                                                                                                                     
    server_name __DOMAIN__;                                                                                                                                                                   
                                                                                                                                                                                              
    ssl_certificate /etc/letsencrypt/live/__DOMAIN__/fullchain.pem;                                                                                                                           
    ssl_certificate_key /etc/letsencrypt/live/__DOMAIN__/privkey.pem;                                                                                                                         
                                                                                                                                                                                              
    # Security                                                                                                                                                                                
    ssl_protocols TLSv1.2 TLSv1.3;                                                                                                                                                            
    ssl_ciphers HIGH:!aNULL:!MD5;                                                                                                                                                             
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;                                                                                                        
    add_header X-Frame-Options DENY always;                                                                                                                                                   
    add_header X-Content-Type-Options nosniff always;                                                                                                                                         
                                                                                                                                                                                              
    # Keep ACME available on 443 as well (optional)                                                                                                                                           
    location /.well-known/acme-challenge/ {                                                                                                                                                   
        root /var/www/html;                                                                                                                                                                   
    }                                                                                                                                                                                         
                                                                                                                                                                                              
    # Guacamole (Tomcat inside container exposes /guacamole/)                                                                                                                                 
    location / {                                                                                                                                                                              
        proxy_pass http://127.0.0.1:8080/guacamole/;                                                                                                                                          
        proxy_http_version 1.1;                                                                                                                                                               
        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 $scheme;                                                                                                                                           
        proxy_buffering off;                                                                                                                                                                  
        proxy_read_timeout 3600s;                                                                                                                                                             
        proxy_send_timeout 3600s;                                                                                                                                                             
                                                                                                                                                                                              
        # WebSockets                                                                                                                                                                          
        proxy_set_header Upgrade $http_upgrade;                                                                                                                                               
        proxy_set_header Connection $connection_upgrade;                                                                                                                                      
    }                                                                                                                                                                                         
}                                                                                                                                                                                             
                                                                                                                                                                                              
# HTTP kept for redirect + ACME (certbot renews against 80)                                                                                                                                   
server {                                                                                                                                                                                      
    listen 80;                                                                                                                                                                                
    server_name __DOMAIN__;                                                                                                                                                                   
    location /.well-known/acme-challenge/ { root /var/www/html; }                                                                                                                             
    location / { return 301 https://$host$request_uri; }                                                                                                                                      
}                                                                                                                                                                                             
EOF                                                                                                                                                                                           
                                                                                                                                                                                              
# Inject the actual domain into the file                                                                                                                                                      
sed -i "s/__DOMAIN__/$DOMAIN/g" "$NGXCONF/guacamole.conf"                                                                                                                                     
                                                                                                                                                                                              
nginx -t && systemctl reload nginx                                                                                                                                                            
                                                                                                                                                                                              
### --- Firewall ---                                                                                                                                                                          
ufw allow 22/tcp                                                                                                                                                                              
ufw allow 80/tcp                                                                                                                                                                              
ufw allow 443/tcp                                                                                                                                                                             
ufw --force enable                                                                                                                                                                            
                                                                                                                                                                                              
### --- Certbot renew (daily) ---                                                                                                                                                             
( crontab -l 2>/dev/null; echo "12 3 * * * certbot renew --quiet && systemctl reload nginx" ) | crontab -                                                                                     
                                                                                                                                                                                              
### --- VNC user (optional workstation target) ---                                                                                                                                            
echo "[INFO] Configuring VNC user: $VNC_USER"                                                                                                                                                 
adduser --disabled-password --gecos "" "$VNC_USER" || true                                                                                                                                    
mkdir -p "/home/$VNC_USER/.vnc"                                                                                                                                                               
echo "Set VNC password for user $VNC_USER:"                                                                                                                                                   
su - "$VNC_USER" -c "vncpasswd"                                                                                                                                                               
                                                                                                                                                                                              
cat > "/home/$VNC_USER/.vnc/xstartup" <<'EOS'                                                                                                                                                 
#!/bin/bash                                                                                                                                                                                   
xrdb $HOME/.Xresources                                                                                                                                                                        
startxfce4 &                                                                                                                                                                                  
EOS                                                                                                                                                                                           
chmod +x "/home/$VNC_USER/.vnc/xstartup"                                                                                                                                                      
chown -R "$VNC_USER:$VNC_USER" "/home/$VNC_USER/.vnc"                                                                                                                                         
                                                                                                                                                                                              
cat > "/etc/systemd/system/vncserver@.service" <<'EOS'                                                                                                                                        
[Unit]                                                                                                                                                                                        
Description=Start TightVNC server at startup                                                                                                                                                  
After=network.target                                                                                                                                                                          
                                                                                                                                                                                              
[Service]                                                                                                                                                                                     
Type=forking                                                                                                                                                                                  
User=%i                                                                                                                                                                                       
PAMName=login                                                                                                                                                                                 
PIDFile=/home/%i/.vnc/%H:1.pid                                                                                                                                                                
ExecStartPre=-/usr/bin/vncserver -kill :1 > /dev/null 2>&1                                                                                                                                    
ExecStart=/usr/bin/vncserver :1                                                                                                                                                               
ExecStop=/usr/bin/vncserver -kill :1                                                                                                                                                          
                                                                                                                                                                                              
[Install]                                                                                                                                                                                     
WantedBy=multi-user.target                                                                                                                                                                    
EOS                                                                                                                                                                                           
                                                                                                                                                                                              
systemctl daemon-reload                                                                                                                                                                       
systemctl enable vncserver@"$VNC_USER"                                                                                                                                                        
systemctl start vncserver@"$VNC_USER"                                                                                                                                                         
                                                                                                                                                                                              
### --- Done ---                                                                                                                                                                              
echo                                                                                                                                                                                          
echo "[INFO] Guacamole is ready at: https://$DOMAIN/"                                                                                                                                         
echo "[INFO] Login: guacadmin"                                                                                                                                                                
echo "[INFO] Password: $ADMIN_PASS"                                                                                                                                                           
echo "[INFO] GUACAMOLE_HOME: $GUAC_HOME"                                                                                                                                                      
echo "[INFO] .env stored at: $ENVFILE (chmod 600)"