Skip to content

Unsupported Reference Scripts

Note

These are not supported scripts/configurations by Tactical RMM, but it's provided here for your reference.

Although these aren't officially supported configurations, we generally will help point you in the right direction. Please use the Discord #unsupported channel to discuss issues replated to these complex installations

General Notes on Proxies and Tactical RMM

Port 443

Make sure websockets option is enabled.

All 3 URL's will need to be configured: rmm, api, mesh

For mesh see the Section 10. TLS Offloading of the MeshCentral 2 User Guide

Port 4222

Is NATS (https://nats.io). You'll need a TCP forwarder as NATS only talks TCP not HTTP.

Traefikv2

Offsite Resource: https://gitlab.com/NiceGuyIT/tactical-goodies/-/tree/main/traefik

This section will assume that by default Traefik will reverse proxy everything on port 443.

Here is a basic Traefik config with docker-composer note the file.directory and file.watch are important.

version: "3.7"
services:
  traefik:
    container_name: traefik24
    image: traefik:v2.4 
    restart: unless-stopped
    command:
      - --entryPoints.http.address=:80
      - --entryPoints.https.address=:443
      - --providers.docker=true
      - --providers.docker.endpoint=unix:///var/run/docker.sock
      - --providers.docker.defaultrule=HostHeader(`{{ index .Labels "com.docker.compose.service" }}.$DOMAINNAME`)
      ## This is important, to load the config for RMM and Mesh
      - --providers.file.directory=rules # Load dynamic configuration from one or more .toml or .yml files in a directory.
      - --providers.file.watch=true # Only works on top level files in the rules folder
      ####
      - --certificatesresolvers.dns-cloudflare.acme.dnschallenge=true
      - --certificatesResolvers.dns-cloudflare.acme.email=$CLOUDFLARE_EMAIL
      - --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json
      - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.provider=cloudflare
      - --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53
    ports:
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
    volumes:
        ##The rules that we will load##
      - $USERDIR/docker/traefik2/rules:/rules 
        ##
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - $USERDIR/docker/traefik2/acme/acme.json:/acme.json
      - $USERDIR/docker/traefik2/traefik.log:/traefik.log
    environment:
      - CF_API_EMAIL=$CLOUDFLARE_EMAIL
      - CF_API_KEY=$CLOUDFLARE_API_KEY
    labels:
      - "traefik.enable=true"
      # HTTP-to-HTTPS Redirect
      - "traefik.http.routers.http-catchall.entrypoints=http"
      - "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      # HTTP Routers
      - "traefik.http.routers.traefik-rtr.entrypoints=https"
      - "traefik.http.routers.traefik-rtr.rule=HostHeader(`traefik.$DOMAINNAME`)"
      - "traefik.http.routers.traefik-rtr.tls=true"
      - "traefik.http.routers.traefik-rtr.tls.domains[0].main=$DOMAINNAME"
      - "traefik.http.routers.traefik-rtr.tls.domains[0].sans=*.$DOMAINNAME"

Before proceding, we need to change the port 443 to 4430 and 80 to 800 because the port 443 and 80 are alredy used by Traefik.

Here is a snippet of the only thing you should modify into docker-compose file of the installation.

  # container for tactical reverse proxy
  tactical-nginx:
    container_name: trmm-nginx
    image: ${IMAGE_REPO}tactical-nginx:${VERSION}
    restart: always
    environment:
      APP_HOST: ${APP_HOST}
      API_HOST: ${API_HOST}
      MESH_HOST: ${MESH_HOST}
      CERT_PUB_KEY: ${CERT_PUB_KEY}
      CERT_PRIV_KEY: ${CERT_PRIV_KEY}
    networks:
      proxy:
        ipv4_address: 172.20.0.20
    ports:
      - "800:80" ## port 800 instead of 80
      - "4430:443" ## port 4430 instead of 443

Once save, make sure you run the docker-compose or installation script at least once, so all the directory structure are created. Once you have your certificate (acme.json) generated by traefikv2 we will be able to extract it for rmm.

Copy the acme.json create by traefik into the root of your rmm directory (In my case its $USERDIR/docker/rmm) which you should have already define. After that we can run this docker to extract the certificates for us.

version: "3.7"
services:
##Copy the acme.json of Traefik2 at volumes: (userdir/docker/rmm in this case)
  traefik-certs-dumper:
    image: ldez/traefik-certs-dumper:v2.7.4
    entrypoint: sh -c '
      apk add jq
      ; while ! [ -e /data/acme.json ]
      || ! [ `jq ".[] | .Certificates | length" /data/acme.json` != 0 ]; do
      sleep 1
      ; done
      && traefik-certs-dumper file --version v2 --watch
      --source /data/acme.json --dest data/certs'
    volumes:
      - $USERDIR/docker/rmm:/data

Once completed, you should have 1 new folder into you rmm directory $USERDIR/docker/rmm/certs in this example. As the installation instruction, we will pass those to the .env

echo "CERT_PUB_KEY=$(sudo base64 -w 0 $USERDIR/docker/rmm/certs/certs/**yourdomaine.com.crt**)" >> .env
echo "CERT_PRIV_KEY=$(sudo base64 -w 0 $USERDIR/docker/rmm/certs/private/**yourdomaine.com.key**)" >> .env

Next we can create 3 rules to tell traefik to correctly route the https and agent For that we will create 2 rules into traefik directory as per it configuration. folder/traefik/rules

create

nano app-mesh.toml

and inside it we add

[http.routers]
  [http.routers.mesh-rtr]
      entryPoints = ["https"]
      rule = "Host(`mesh.**yourdomain.com**`)"
      service = "mesh-svc"
##middleware with 2fa
[http.services]
  [http.services.mesh-svc]
    [http.services.mesh-svc.loadBalancer]
      passHostHeader = true
      [[http.services.mesh-svc.loadBalancer.servers]]
        url = "https://**xxx.xxx.xxx.xxx**:4430" # or whatever your external host's IP is

create

nano app-meshagent.toml

and inside it we add

[http.routers]
  [http.routers.mesh-rtr1]
      entryPoints = ["https"]
      rule = """Host(`mesh.**yourdomain.com**`) &&
        PathPrefix( `/agent.ashx`, `/meshrelay.ashx`, ) &&
        Headers(`X-Forwarded-Proto`, `wss`) """
    ##Don't add middle where, the agent wont work.    
[http.services]
  [http.services.mesh-svc1]
    [http.services.mesh-svc.loadBalancer]
      passHostHeader = true
      [[http.services.mesh-svc1.loadBalancer.servers]]
        url = "https://**xxx.xxx.xxx.xxx**:4430" # or whatever your external host's IP is

create

nano app-rmm.toml

and inside it we add

[http.routers]
  [http.routers.rmm-rtr]
      entryPoints = ["https"]
      rule = "Host(`rmm.**yourdomain.com**`)"
      service = "rmm-svc"

      ##middleware with 2fa

[http.services]
  [http.services.rmm-svc]
    [http.services.rmm-svc.loadBalancer]
      passHostHeader = true
      [[http.services.rmm-svc.loadBalancer.servers]]
        url = "https://xxx.xxx.xxx.xxx:4430" # or whatever your external host's IP:port is

That it, you can now restart Tactical rmm and mesh.yourdomain.com should work, same for the agent. Please note that if you have a middleware with 2FA you can still use it with the inside mesh.toml but do not add it with the agent.

HAProxy

Check/Change the mesh central config.json, some of the values may be set already, CertUrl must be changed to point to the HAProxy server.

Meshcentral Adjustment

Credit to @bradhawkins

Edit Meshcentral config

nano /meshcentral/meshcentral-data/config.json

Insert this (modify HAProxyIP to your network)

{
  "settings": {
    "Port": 4430,
    "AliasPort": 443,
    "RedirPort": 800,
    "TlsOffload": "127.0.0.1",
  },
  "domains": {
    "": {
      "CertUrl": "https://HAProxyIP:443/",
    }
  }
}

Restart meshcentral

service meshcentral restart

HAProxy Config

The order of use_backend is important Tactical-Mesh-WebSocket_ipvANY must be before Tactical-Mesh_ipvANY The values of timeout connect, timeout server, timeout tunnel in Tactical-Mesh-WebSocket have been configured to maintain a stable agent connection, however you may need to adjust these values to suit your environment.

frontend HTTPS-merged
    bind            0.0.0.0:443 name 0.0.0.0:443   ssl crt-list /var/etc/haproxy/HTTPS.crt_list  #ADJUST THIS TO YOUR OWN SSL CERTIFICATES
    mode            http
    log         global
    option          socket-stats
    option          dontlognull
    option          http-server-close
    option          forwardfor
    acl https ssl_fc
    http-request set-header     X-Forwarded-Proto http if !https
    http-request set-header     X-Forwarded-Proto https if https
    timeout client      30000
    acl         RMM var(txn.txnhost) -m sub -i rmm.example.com
    acl         aclcrt_RMM  var(txn.txnhost) -m reg -i ^([^\.]*)\.example\.com(:([0-9]){1,5})?$
    acl         API var(txn.txnhost) -m sub -i api.example.com
    acl         aclcrt_API  var(txn.txnhost) -m reg -i ^([^\.]*)\.example\.com(:([0-9]){1,5})?$
    acl         is_websocket    hdr(Upgrade) -i WebSocket
    acl         is_mesh var(txn.txnhost) -m beg -i mesh.example.com
    acl         aclcrt_MESH-WebSocket   var(txn.txnhost) -m reg -i ^([^\.]*)\.example\.com(:([0-9]){1,5})?$
    acl         MESH    var(txn.txnhost) -m sub -i mesh.example.com
    acl         aclcrt_MESH var(txn.txnhost) -m reg -i ^([^\.]*)\.example\.com(:([0-9]){1,5})?$
    #PUT OTHER USE_BACKEND IN HERE
    use_backend Tactical_ipvANY  if  RMM aclcrt_RMM
    use_backend Tactical_ipvANY  if  API aclcrt_API
    use_backend Tactical-Mesh-WebSocket_ipvANY  if  is_websocket is_mesh aclcrt_MESH-WebSocket
    use_backend Tactical-Mesh_ipvANY  if  MESH aclcrt_MESH

frontend http-to-https
    bind            0.0.0.0:80   
    mode            http
    log         global
    option          http-keep-alive
    timeout client      30000
    http-request redirect scheme https 


backend Tactical_ipvANY
    mode            http
    id          100
    log         global
    timeout connect     30000
    timeout server      30000
    retries         3
    option          httpchk GET / 
    server          tactical 192.168.10.123:443 id 101 ssl check inter 1000  verify none 


backend Tactical-Mesh-WebSocket_ipvANY
    mode            http
    id          113
    log         global
    timeout connect     3000
    timeout server      3000
    retries         3
    timeout tunnel      3600000
    http-request add-header X-Forwarded-Host %[req.hdr(Host)] 
    http-request add-header X-Forwarded-Proto https 
    server          tactical 192.168.10.123:443 id 101 ssl  verify none 

backend Tactical-Mesh_ipvANY
    mode            http
    id          112
    log         global
    timeout connect     15000
    timeout server      15000
    retries         3
    option          httpchk GET / 
    timeout tunnel      15000
    http-request add-header X-Forwarded-Host %[req.hdr(Host)] 
    http-request add-header X-Forwarded-Proto https 
    server          tactical 192.168.10.123:443 id 101 ssl check inter 1000  verify none 

fail2ban

Install fail2ban

sudo apt install -y fail2ban

Set Tactical fail2ban filter conf File

tacticalfail2banfilter="$(cat << EOF
[Definition]
failregex = ^<HOST>.*400.17.*$
ignoreregex = ^<HOST>.*200.*$
EOF
)"
sudo echo "${tacticalfail2banfilter}" > /etc/fail2ban/filter.d/tacticalrmm.conf

Set Tactical fail2ban jail conf File

tacticalfail2banjail="$(cat << EOF
[tacticalrmm]
enabled = true
port = 80,443
filter = tacticalrmm
action = iptables-allports[name=tactical]
logpath = /rmm/api/tacticalrmm/tacticalrmm/private/log/access.log
maxretry = 3
bantime = 14400
findtime = 14400
EOF
)"
sudo echo "${tacticalfail2banjail}" > /etc/fail2ban/jail.d/tacticalrmm.local

Restart fail2ban

sudo systemctl restart fail2ban

Using purchased SSL certs instead of LetsEncrypt wildcards

Credit to @dinger1986

How to change certs used by Tactical RMM to purchased ones (this can be a wildcard cert).

You need to add the certificate private key and public keys to the following files:

/etc/nginx/sites-available/rmm.conf

/etc/nginx/sites-available/meshcentral.conf

/etc/nginx/sites-available/frontend.conf

/rmm/api/tacticalrmm/tacticalrmm/local_settings.py

  1. create a new folder for certs and allow tactical user permissions (assumed to be tactical)

    sudo mkdir /certs
    sudo chown -R tactical:tactical /certs"
    
  2. Now move your certs into that folder.

  3. Open the api file and add the api certificate or if its a wildcard the directory should be /certs/EXAMPLE.COM/

    sudo nano /etc/nginx/sites-available/rmm.conf
    

    replace

    ssl_certificate /etc/letsencrypt/live/EXAMPLE.COM/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/EXAMPLE.COM/privkey.pem;
    

    with

    ssl_certificate /certs/api.EXAMPLE.COM/fullchain.pem;
    ssl_certificate_key /certs/api.EXAMPLE.COM/privkey.pem;
    
  4. Repeat the process for

    /etc/nginx/sites-available/meshcentral.conf
    /etc/nginx/sites-available/frontend.conf
    

    but change api. to: mesh. and rmm. respectively.

  5. Add the following to the last lines of /rmm/api/tacticalrmm/tacticalrmm/local_settings.py

    nano /rmm/api/tacticalrmm/tacticalrmm/local_settings.py
    

    add

    CERT_FILE = "/certs/api.EXAMPLE.COM/fullchain.pem"
    KEY_FILE = "/certs/api.EXAMPLE.COM/privkey.pem"
    
  6. Regenerate Nats Conf

    cd /rmm/api/tacticalrmm
    source ../env/bin/activate
    python manage.py reload_nats
    
  7. Restart services

    sudo systemctl restart rmm celery celerybeat nginx nats nats-api
    

Use certbot to do acme challenge over http

The standard SSL cert process in Tactical uses a DNS challenge that requires dns txt files to be updated in your public DNS with every cert renewal.

The below script uses http challenge on the 3 separate ssl certs, one for each subdomain: rmm, api, mesh. They still have the same 3 month expiry. Restart the Tactical RMM server about every 2.5 months (80 days) for auto-renewed certs to become active.

Note

Your Tactical RMM server will need to have TCP Port: 80 exposed to the internet

#!/bin/bash

###Set colours same as Tactical RMM install and Update
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'

### Ubuntu 20.04 Check

UBU20=$(grep 20.04 "/etc/"*"release")
if ! [[ $UBU20 ]]; then
  echo -ne "\033[0;31mThis script will only work on Ubuntu 20.04\e[0m\n"
  exit 1
fi

cls() {
  printf "\033c"
}

print_green() {
  printf >&2 "${GREEN}%0.s-${NC}" {1..80}
  printf >&2 "\n"
  printf >&2 "${GREEN}${1}${NC}\n"
  printf >&2 "${GREEN}%0.s-${NC}" {1..80}
  printf >&2 "\n"
}

cls

### Set variables for domains

while [[ $rmmdomain != *[.]*[.]* ]]
do
echo -ne "${YELLOW}Enter the subdomain used for the backend (e.g. api.example.com)${NC}: "
read rmmdomain
done

while [[ $frontenddomain != *[.]*[.]* ]]
do
echo -ne "${YELLOW}Enter the subdomain used for the frontend (e.g. rmm.example.com)${NC}: "
read frontenddomain
done

while [[ $meshdomain != *[.]*[.]* ]]
do
echo -ne "${YELLOW}Enter the subdomain used for meshcentral (e.g. mesh.example.com)${NC}: "
read meshdomain
done

echo -ne "${YELLOW}Enter the current root domain (e.g. example.com or example.co.uk)${NC}: "
read rootdomain


### Setup Certificate Variables
CERT_PRIV_KEY=/etc/letsencrypt/live/${rootdomain}/privkey.pem
CERT_PUB_KEY=/etc/letsencrypt/live/${rootdomain}/fullchain.pem

### Make Letsencrypt directories

sudo mkdir /var/www/letsencrypt
sudo mkdir /var/www/letsencrypt/.mesh
sudo mkdir /var/www/letsencrypt/.rmm
sudo mkdir /var/www/letsencrypt/.api

### Remove config files for nginx

sudo rm /etc/nginx/sites-available/rmm.conf
sudo rm /etc/nginx/sites-available/meshcentral.conf
sudo rm /etc/nginx/sites-available/frontend.conf
sudo rm /etc/nginx/sites-enabled/rmm.conf
sudo rm /etc/nginx/sites-enabled/meshcentral.conf
sudo rm /etc/nginx/sites-enabled/frontend.conf

### Setup tactical nginx config files for letsencrypt

nginxrmm="$(cat << EOF
server_tokens off;
upstream tacticalrmm {
    server unix:////rmm/api/tacticalrmm/tacticalrmm.sock;
}
map \$http_user_agent \$ignore_ua {
    "~python-requests.*" 0;
    "~go-resty.*" 0;
    default 1;
}
server {
    listen 80;
    server_name ${rmmdomain};
        location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt/.api/;}
    location / {
    return 301 https://\$server_name\$request_uri;}
}
server {
    listen 443 ssl;
    server_name ${rmmdomain};
    client_max_body_size 300M;
    access_log /rmm/api/tacticalrmm/tacticalrmm/private/log/access.log;
    error_log /rmm/api/tacticalrmm/tacticalrmm/private/log/error.log;
    ssl_certificate ${CERT_PUB_KEY};
    ssl_certificate_key ${CERT_PRIV_KEY};
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';

    location /static/ {
        root /rmm/api/tacticalrmm;
    }
    location /private/ {
        internal;
        add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
        alias /rmm/api/tacticalrmm/tacticalrmm/private/;
    }
location ~ ^/ws/ {
        proxy_pass http://unix:/rmm/daphne.sock;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_redirect     off;
        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-Host $server_name;
}
    location /saltscripts/ {
        internal;
        add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
        alias /srv/salt/scripts/userdefined/;
    }
    location /builtin/ {
        internal;
        add_header "Access-Control-Allow-Origin" "https://${frontenddomain}";
        alias /srv/salt/scripts/;
    }
    location ~ ^/(natsapi) {
        allow 127.0.0.1;
        deny all;
        uwsgi_pass tacticalrmm;
        include     /etc/nginx/uwsgi_params;
        uwsgi_read_timeout 500s;
        uwsgi_ignore_client_abort on;
    }
    location / {
        uwsgi_pass  tacticalrmm;
        include     /etc/nginx/uwsgi_params;
        uwsgi_read_timeout 9999s;
        uwsgi_ignore_client_abort on;
    }
}
EOF
)"
echo "${nginxrmm}" | sudo tee /etc/nginx/sites-available/rmm.conf > /dev/null


nginxmesh="$(cat << EOF
server {
  listen 80;
  server_name ${meshdomain};
      location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt/.mesh/;}
    location / {
  return 301 https://\$server_name\$request_uri;}
}
server {
    listen 443 ssl;
    proxy_send_timeout 330s;
    proxy_read_timeout 330s;
    server_name ${meshdomain};
    ssl_certificate ${CERT_PUB_KEY};
    ssl_certificate_key ${CERT_PRIV_KEY};
    ssl_session_cache shared:WEBSSL:10m;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    location / {
        proxy_pass http://127.0.0.1:4430/;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Forwarded-Host \$host:\$server_port;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }
}
EOF
)"
echo "${nginxmesh}" | sudo tee /etc/nginx/sites-available/meshcentral.conf > /dev/null



nginxfrontend="$(cat << EOF
server {
    server_name ${frontenddomain};
    charset utf-8;
    location / {
        root /var/www/rmm/dist;
        try_files \$uri \$uri/ /index.html;
        add_header Cache-Control "no-store, no-cache, must-revalidate";
        add_header Pragma "no-cache";
    }
    error_log  /var/log/nginx/frontend-error.log;
    access_log /var/log/nginx/frontend-access.log;
    listen 443 ssl;
    ssl_certificate ${CERT_PUB_KEY};
    ssl_certificate_key ${CERT_PRIV_KEY};
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
}
server {
    listen 80;
    server_name ${frontenddomain};
    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt/.rmm/;}
    location / {
    return 301 https://\$host\$request_uri;}
}
EOF
)"
echo "${nginxfrontend}" | sudo tee /etc/nginx/sites-available/frontend.conf > /dev/null

### Relink nginx config files

sudo ln -s /etc/nginx/sites-available/rmm.conf /etc/nginx/sites-enabled/rmm.conf
sudo ln -s /etc/nginx/sites-available/meshcentral.conf /etc/nginx/sites-enabled/meshcentral.conf
sudo ln -s /etc/nginx/sites-available/frontend.conf /etc/nginx/sites-enabled/frontend.conf

### Restart nginx

sudo systemctl restart nginx


### Get letsencrypt Certs

sudo letsencrypt certonly --webroot -w /var/www/letsencrypt/.mesh/ -d ${meshdomain}
sudo letsencrypt certonly --webroot -w /var/www/letsencrypt/.rmm/ -d ${frontenddomain}
sudo letsencrypt certonly --webroot -w /var/www/letsencrypt/.api/ -d ${rmmdomain}

### Ensure letsencrypt Permissions are correct
sudo chown ${USER}:${USER} -R /etc/letsencrypt
sudo chmod 775 -R /etc/letsencrypt

### Set variables for new certs

CERT_PRIV_KEY_API=/etc/letsencrypt/live/${rmmdomain}/privkey.pem
CERT_PUB_KEY_API=/etc/letsencrypt/live/${rmmdomain}/fullchain.pem
CERT_PRIV_KEY_RMM=/etc/letsencrypt/live/${frontenddomain}/privkey.pem
CERT_PUB_KEY_RMM=/etc/letsencrypt/live/${frontenddomain}/fullchain.pem
CERT_PRIV_KEY_MESH=/etc/letsencrypt/live/${meshdomain}/privkey.pem
CERT_PUB_KEY_MESH=/etc/letsencrypt/live/${meshdomain}/fullchain.pem

### Replace certs in files

rmmlocalsettings="$(cat << EOF
CERT_FILE = "${CERT_PUB_KEY_API}"
KEY_FILE = "${CERT_PRIV_KEY_API}"
EOF
)"
echo "${rmmlocalsettings}" | tee --append /rmm/api/tacticalrmm/tacticalrmm/local_settings.py > /dev/null

sudo sed -i "s|${CERT_PRIV_KEY}|${CERT_PRIV_KEY_API}|g" /etc/nginx/sites-available/rmm.conf
sudo sed -i "s|${CERT_PUB_KEY}|${CERT_PUB_KEY_API}|g" /etc/nginx/sites-available/rmm.conf
sudo sed -i "s|${CERT_PRIV_KEY}|${CERT_PRIV_KEY_MESH}|g" /etc/nginx/sites-available/meshcentral.conf
sudo sed -i "s|${CERT_PUB_KEY}|${CERT_PUB_KEY_MESH}|g" /etc/nginx/sites-available/meshcentral.conf
sudo sed -i "s|${CERT_PRIV_KEY}|${CERT_PRIV_KEY_RMM}|g" /etc/nginx/sites-available/frontend.conf
sudo sed -i "s|${CERT_PUB_KEY}|${CERT_PUB_KEY_RMM}|g" /etc/nginx/sites-available/frontend.conf

### Remove Wildcard Cert

rm -r /etc/letsencrypt/live/${rootdomain}/
rm -r /etc/letsencrypt/archive/${rootdomain}/
rm /etc/letsencrypt/renewal/${rootdomain}.conf


### Regenerate Nats Conf
cd /rmm/api/tacticalrmm
source ../env/bin/activate
python manage.py reload_nats

### Restart services

for i in rmm celery celerybeat nginx nats nats-api
do
printf >&2 "${GREEN}Restarting ${i} service...${NC}\n"
sudo systemctl restart ${i}
done


###Renew certs can be done by sudo letsencrypt renew (this should automatically be in /etc/cron.d/certbot)

Using your own certs with Docker

Let's Encrypt is the only officially supported method of obtaining wildcard certificates. Publicly signed certificates should work but have not been fully tested.

If you are providing your own publicly signed certificates, ensure you download the full chain (combined CA/Root + Intermediary) certificate in pem format. If certificates are not provided, a self-signed certificate will be generated and most agent functions won't work.

Restricting Access to rmm.EXAMPLE.COM

Limit access to Tactical RMM's administration panel in nginx to specific locations

Using DNS

  1. Create a file allowed-domain.list which contains the DNS names you want to grant access to your rmm:

    Edit /etc/nginx/allowed-domain.list and add

    nom1.dyndns.tv
    nom2.dyndns.tv
    
  2. Create a bash script domain-resolver.sh which do the DNS lookups for you:

    Edit /etc/nginx/domain-resolver.sh

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    #!/usr/bin/env bash
    filename="$1"
    while read -r line
    do
            ddns_record="$line"
            if [[ !  -z  $ddns_record ]]; then
                    resolved_ip=getent ahosts $line | awk '{ print $1 ; exit }'
                    if [[ !  -z  $resolved_ip ]]; then
                            echo "allow $resolved_ip;# from $ddns_record"
                    fi
            fi
    done < "$filename"
    
  3. Give the right permission to this script chmod +x /etc/nginx/domain-resolver.sh

  4. Add a cron job which produces a valid nginx configuration and restarts nginx:

    /etc/cron.hourly/domain-resolver

    1
    2
    3
    #!/usr/bin/env bash
    /etc/nginx/domain-resolver.sh /etc/nginx/allowed-domain.list > /etc/nginx//allowed-ips-from-domains.conf
    service nginx reload > /dev/null 2>&1
    

    This can be a hourly, daily or monthly job or you can have it run at a specific time.

  5. Give the right permission to this script chmod +x /etc/cron.hourly/domain-resolver

  6. When run it will give something like this

    Edit /etc/nginx//allowed-ips-from-domains.conf

    allow xxx.xxx.xxx.xxx;# from maison.nom1.dyndns.tv
    allow xxx.xxx.xxx.xxx;# from maison.nom2.dyndns.tv
    
  7. Update your nginx configuration to take this output into account:

    Edit /etc/nginx/sites-enabled/frontend.conf

    server {
        server_name rmm.example.com;
        charset utf-8;
        location / {
            root /var/www/rmm/dist;
            try_files $uri $uri/ /index.html;
            add_header Cache-Control "no-store, no-cache, must-revalidate";
            add_header Pragma "no-cache";
        }
        error_log  /var/log/nginx/frontend-error.log;
        access_log /var/log/nginx/frontend-access.log;
        include /etc/nginx/allowed-ips-from-domains.conf;
        deny all;
        listen 443 ssl;
        listen [::]:443 ssl;
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
    }
    
    server {
        if ($host = rmm.example.com) {
            return 301 https://$host$request_uri;
        }
    
        listen 80;
        listen [::]:80;
        server_name rmm.example.com;
        return 404;
    }
    

Using a fixed IP

  1. Create a file containg the fixed IP address (where xxx.xxx.xxx.xxx must be replaced by your real IP address)

    Edit /etc/nginx//allowed-ips.conf

    # Private IP address
    allow 192.168.0.0/16;
    allow 172.16.0.0/12;
    allow 10.0.0.0/8;
    # Public fixed IP address
    allow xxx.xxx.xxx.xxx
    
  2. Update your nginx configuration to take this output into account:

    Edit /etc/nginx/sites-enabled/frontend.conf

    server {
        server_name rmm.example.com;
        charset utf-8;
        location / {
            root /var/www/rmm/dist;
            try_files $uri $uri/ /index.html;
            add_header Cache-Control "no-store, no-cache, must-revalidate";
            add_header Pragma "no-cache";
        }
        error_log  /var/log/nginx/frontend-error.log;
        access_log /var/log/nginx/frontend-access.log;
    include /etc/nginx/allowed-ips;
        deny all;
        listen 443 ssl;
        listen [::]:443 ssl;
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
    }
    
    server {
        if ($host = rmm.example.com) {
            return 301 https://$host$request_uri;
        }
    
        listen 80;
        listen [::]:80;
        server_name rmm.example.com;
        return 404;
    }