Raja's Exocortex

Apache Guacamole

  1. Apache Guacamole is a Remote Access Service which can connect to internal RDP, SSH, VNC, etc services via HTTPS.
  2. End users need a HTML5 browser and can connect to internal servers. VPN or port forwarding to internal services is not required.
  3. Only the Guacamole HTTPS service needs to be exposed to the internet.
  4. Use of external authentication like traefik-forward-auth is recommended. Guacamole is configured for HTTP header authentication from the upstream ingress controller.

Installation

Apache Guacamole consists of three services

  1. guacd - Actual service which connects to machines for remote access. It does not handle authentication.
  2. guacamole - Web frontend which handles authentication and others
  3. database - For storing configuration, users and request data
# Filename: /srv/ras.smounesh.in/docker-compose.yml
# Purpose: to start guacamole docker container along with guad and postgres database
# Authentication: it uses header authentication the username header comes from traefik-forward-auth
#
# Note1: Database will not be initialized with schema during first run.
#       Use this command to generate sql file.
#          "docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > initdb.sql"
#
#       Copy the initdb.sql file inside postgres container:
#           "docker-compose cp initdb.sql postgres:/tmp"
#
#       Exec into the postgres container:
#           "docker-compose exec -it postgres bash"
#
#       Use this sql file to initialize the database:
#           "psql -U ${POSTGRES_USER} -d ${POSTGRES_DB} < /tmp/initdb.sql"
#
# Note2: traffic is proxied from traefik in ns1. 

version: "2.4"
name: guacamole

services:
  guacamole:
    image: guacamole/guacamole
    container_name: ras.bitsathy.ac.in
    restart: always

    mem_limit: 4G

    ports:
      - 8080:8080

    environment:
      HEADER_ENABLED: true
      HTTP_AUTH_HEADER: X-Forwarded-User

      REMOTE_IP_VALVE_ENABLED: true

      GUACD_HOSTNAME: guacd
      GUACD_PORT: 4822

      POSTGRES_HOSTNAME: postgres
      POSTGRES_PORT: 5432
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DATABASE: ${POSTGRES_DB}

    depends_on:
      postgres:
        condition: service_healthy

    labels:
      - com.centurylinklabs.watchtower.enable=true
      # - traefik.enable=true

      # see ns1:/opt/traefik.ns1/conf.d/70-ras.bitsathy.ac.in.yaml
      # path /guacamole permanent redirect
      # - traefik.http.routers.guacamole-redirectregex.rule=Host(`ras.bitsathy.ac.in`)
      # - traefik.http.routers.guacamole-redirectregex.tls=true
      # - traefik.http.routers.guacamole-redirectregex.tls.certresolver=lets-encrypt
      # - traefik.http.routers.guacamole-redirectregex.middlewares=guacamole-redirectregex
      # - traefik.http.middlewares.guacamole-redirectregex.redirectregex.regex=^https://ras.bitsathy.ac.in/(.*)
      # - traefik.http.middlewares.guacamole-redirectregex.redirectregex.replacement=https://ras.bitsathy.ac.in/guacamole/${1}
      # - traefik.http.middlewares.guacamole-redirectregex.redirectregex.permanent=true

      # add authResponseHeaders=X-Forwarded-User in traefik-forward-auth config
      # - traefik.http.routers.guacamole.rule=Host(`ras.bitsathy.ac.in`) && PathPrefix(`/guacamole`)
      # - traefik.http.routers.guacamole.tls=true
      # - traefik.http.routers.guacamole.tls.certresolver=lets-encrypt
      # - traefik.http.routers.guacamole.middlewares=google-oauth2@file

  guacd:
    image: guacamole/guacd
    container_name: guacd-guacamole.smounesh.in
    restart: always

    mem_limit: 4G

    labels:
      - com.centurylinklabs.watchtower.enable=true

  postgres:
    image: postgres:15
    container_name: postgres-guacamole.smounesh.in
    restart: always

    mem_limit: 4G

    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}

    volumes:
      - ./pg-data:/var/lib/postgresql/data

    healthcheck:
      test: pg_isready -U ${POSTGRES_USER}
      start_period: 5s
      interval: 5s
      timeout: 5s
      retries: 55

    labels:
      - com.centurylinklabs.watchtower.enable=true

Traefik-forward-auth

  1. By default traefik-forward-auth middleware do not pass http headers after authentication.
  2. These changes will get the job done
# Traefik v2 dynamic config fragment to expose traefik-forward-auth
# Filename: /opt/traefik/conf.d/20-accounts.bitsathy.ac.in.yaml
# URL: https://accounts.bitsathy.ac.in

http:
  services:
    google-oauth2:
      loadBalancer:
        servers:
          - url: http://localhost:4181

  middlewares:
    google-oauth2:
      forwardAuth:
        address: "http://localhost:4181"
        authResponseHeaders:
          - X-Forwarded-User

  routers:
    google-oauth2:
      rule: Host(`accounts.bitsathy.ac.in`)
      entrypoints:
        - https
      middlewares:
        - google-oauth2
      service: google-oauth2
      tls:
        certResolver: lets-encrypt
  1. Proxy pass the traffic to gucamole using yaml config file
# Traefik v2 dynamic config fragment to expose ras.bitsathy.ac.in
# Filename: /opt/traefik/conf.d/70-ras.bitsathy.ac.in
# URL: https://ras.bitsathy.ac.in

http:
  services:
    ras:
      loadBalancer:
        servers:
          - url: http://10.200.2.170:8080

  middlewares:
    ras-redirectregex:
      redirectregex:
        regex: "^https://ras.bitsathy.ac.in/(.*)"
        replacement: "https://ras.bitsathy.ac.in/guacamole"
        permanent: true

  routers:
    ras-redirectregex:
      rule: Host(`ras.bitsathy.ac.in`)
      middlewares: ras-redirectregex
      service: ras
      tls:
        certResolver: lets-encrypt

    ras:
      rule: Host(`ras.bitsathy.ac.in`) && PathPrefix(`/guacamole`)
      middlewares: google-oauth2@file
      service: ras
      tls:
        certResolver: lets-encrypt

Reference

  1. https://guacamole.apache.org/doc/gug/
  2. https://hub.docker.com/r/guacamole/guacamole

#TODO: clean up required.