Categories
Docker linux Security Windows Server

Setting up RDPGW in docker

I was looking into setting up an instance of RDPWG for a client.

The client currently has a bunch of Windows server VMs in Workgroup which they currently connect to on port 3389 directly exposed to the internet which is obviously a massive security issue.
Additionally, the RDP port is often blocked on corporate networks.

The better option would be to set up an Active Directory domain and an RDP Gateway server, but as a quick alternative while the client decides if the cost is worth it I set up an RDP Gateway using the Open source project RDPGW.

The project itself is very interesting but the documentation is unfortunately seriously lacking and it took me quite a bit of effort to get everything set up, so I wanted to share the results in case it helps someone.

I didn’t find a way to get RDPGW to work behind a reverse proxy, so in this setup you need 2 public IP address if you intend to host Keycloak on port 443 unfortunately.

Setup

This is the docker-compose.yml file:

version: "3.9"
services:
  postgres:
    container_name: db
    image: "postgres:14.4"
    restart: always
    healthcheck:
      test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "postgres" ]
      timeout: 45s
      interval: 10s
      retries: 10
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
      #- ./sql:/docker-entrypoint-initdb.d/:ro # turn it on, if you need run init DB
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: keycloak
      POSTGRES_HOST: postgres
    networks:
      - pgsql

  keycloak:
    container_name: keycloak
    image: quay.io/keycloak/keycloak
    command: ['start', '--proxy', "edge"]
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      JAVA_OPTS_APPEND: -Dkeycloak.profile.feature.upload_scripts=enabled
      KC_DB_PASSWORD: postgres
      KC_DB_URL: "jdbc:postgresql://postgres/keycloak"
      KC_DB_USERNAME: postgres
      KC_DB: postgres
      KC_HEALTH_ENABLED: 'true'
      KC_HTTP_ENABLED: 'true'
      KC_METRICS_ENABLED: 'true'
      KC_HOSTNAME_STRICT_HTTPS: true
      KC_HOSTNAME: rdgateway-keycloak.example.com
      PROXY_ADDRESS_FORWARDING: 'true'
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: password
    healthcheck:
      test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080;echo -e \"GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3"]
      interval: 10s
      retries: 10
      start_period: 20s
      timeout: 10s
    ports:
      - "8080:8080"
      - "8787:8787" # debug port
    networks:
      - pgsql
      - keycloak

  rdpgw:
    image: bolkedebruin/rdpgw:latest
    restart: always
    container_name: rdpgw
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./rdpgw.yaml:/opt/rdpgw/rdpgw.yaml
    depends_on:
      keycloak:
        condition: service_healthy
    networks:
      - keycloak

networks:
  pgsql:
    driver: bridge
  keycloak:
    driver: bridge

you need to create a postgres_data directory (or use a volume).

This is the rdpgw.yaml file (look a the documentation for information on secrets):

Server:
 Tls: auto
 GatewayAddress: rdgateway.example.com
 Port: 443
 Hosts:
  - rdshost-1.com:3389
  - rdshost-2.com:3389
 RoundRobin: any
 SessionKey: changeme
 SessionEncryptionKey: changeme
Authentication:
 - openid
OpenId:
 ProviderUrl: https://rdgateway-keycloak.example.com/realms/rdpgw
 ClientId: rdpgw
 ClientSecret: 01cd304c-6f43-4480-9479-618eb6fd578f
Client:
 UsernameTemplate: "{{ username }}"
 NetworkAutoDetect: 1
 BandwidthAutoDetect: 1
 ConnectionType: 6
 SplitUserDomain: true
Security:
  PAATokenSigningKey: changeme
  UserTokenEncryptionKey: changeme
  VerifyClientIp: false
Caps:
 TokenAuth: true
 IdleTimeout: 10
 EnablePrinter: true
 EnablePort: true
 EnablePnp: true
 EnableDrive: true
 EnableClipboard: true

The “hosts” section describes all the hosts the server allows access to.

Configuration

After all the files have been created in your chosen directory, you can run

docker compose up -d

After some time it will bring up Postgres and then Keycloak. RDPGW will not come up before we’ve create the new realm in Keycloak.

You will need to handle the reverse proxying of Keycloak on your own, it is outside the scope of this article.

Login to Keycloak with the user you’ve defined in the configuration file and on the top left, create a new Realm:

There, you can import the Realm configuration from here.

It will create a realm named “rdpgw” with a single user named “admin@rdpgw” with password “admin” (which you should change promptly)

Once this is done, the RDPGW container should come up.

You should be able to use https://rdgateway.example.com/connect and login with the user “admin@rdpgw”.
It will download a .rdp file, which you should be able to open to connect to the first host in the list.

If you want to connect to a specific host, you should use https://rdgateway.example.com/connect?host=destination-host-url1.com

Categories
Docker linux Security

Traefik 2 and Nextcloud

I use Traefik and Nextcloud on docker and it took me a few tries to get it to the point where nextcloud would not complain about configuration issues.

Here is the configuration I ended up with:

- "traefik.enable=true"
- "traefik.docker.network=webgateway"
- "traefik.http.routers.nextcloud.middlewares=nextcloud,nextcloud_redirect"
- "traefik.http.routers.nextcloud.rule=Host(`nextcloud.fqdn.com`)"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=mydnschallenge"
- "traefik.http.middlewares.nextcloud.headers.customFrameOptionsValue=ALLOW-FROM https://fqdn.com"
- "traefik.http.middlewares.nextcloud.headers.contentSecurityPolicy=frame-ancestors 'self' fqdn.com *.fqdn.com"
- "traefik.http.middlewares.nextcloud.headers.stsSeconds=155520011"
- "traefik.http.middlewares.nextcloud.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.nextcloud.headers.stsPreload=true"
- "traefik.http.middlewares.nextcloud.headers.referrerPolicy=same-origin"
- "traefik.http.middlewares.nextcloud.headers.customFrameOptionsValue=SAMEORIGIN"
- "traefik.http.middlewares.nextcloud_redirect.redirectregex.regex=/.well-known/(card|cal)dav"
- "traefik.http.middlewares.nextcloud_redirect.redirectregex.replacement=/remote.php/dav/"

This answers a question that’s been asked a few times on this post, which is “how to configure HSTS on Traefik 2”