Add inital docker container support

This commit is contained in:
Josh Krawczyk 2020-02-12 11:18:43 -05:00
parent 70282fb091
commit c0edbaeee7
21 changed files with 583 additions and 7 deletions

View File

@ -101,7 +101,7 @@ class Agent(models.Model):
if "kwargs" in kwargs:
json.update({"kwarg": kwargs["kwargs"]})
resp = requests.post(
"http://127.0.0.1:8123/run", json=[json], timeout=kwargs["timeout"]
"http://" + settings.SALT_HOST + ":8123/run", json=[json], timeout=kwargs["timeout"]
)
return resp
@ -121,7 +121,7 @@ class Agent(models.Model):
json.update({"arg": kwargs["arg"]})
if "kwargs" in kwargs:
json.update({"kwarg": kwargs["kwargs"]})
resp = requests.post("http://127.0.0.1:8123/run", json=[json])
resp = requests.post("http://" + settings.SALT_HOST + ":8123/run", json=[json])
return resp
@staticmethod
@ -129,7 +129,7 @@ class Agent(models.Model):
session = requests.Session()
session.post(
"http://127.0.0.1:8123/login",
"http://" + settings.SALT_HOST + ":8123/login",
json={
"username": settings.SALT_USERNAME,
"password": settings.SALT_PASSWORD,
@ -137,7 +137,7 @@ class Agent(models.Model):
},
)
return session.get(f"http://127.0.0.1:8123/jobs/{jid}")
return session.get(f"http://" + settings.SALT_HOST + ":8123/jobs/{jid}")
@staticmethod
def get_github_versions():

View File

@ -2,13 +2,14 @@ from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
from celery.schedules import crontab
from django.conf import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tacticalrmm.settings")
app = Celery("tacticalrmm", backend="redis://localhost", broker="redis://localhost")
app = Celery("tacticalrmm", backend="redis://" + settings.REDIS_HOST, broker="redis://" + settings.REDIS_HOST)
# app.config_from_object('django.conf:settings', namespace='CELERY')
app.broker_url = "redis://localhost:6379"
app.result_backend = "redis://localhost:6379"
app.broker_url = "redis://" + settings.REDIS_HOST + ":6379"
app.result_backend = "redis://" + settings.REDIS_HOST + ":6379"
app.accept_content = ["application/json"]
app.result_serializer = "json"
app.task_serializer = "json"

21
docker/.env.example Normal file
View File

@ -0,0 +1,21 @@
MESH_HOST=mesh.example.com
MESH_USER=mesh
MESH_PASS=meshpass
POSTGRES_USER=postgres
POSTGRES_PASS=pass
POSTGRES_HOST=db
APP_HOST=app.example.com
API_HOST=api.example.com
REDIS_HOST=redis
SALT_HOST=salt
SALT_USER=saltapi
SALT_PASS=password
ADMIN_URL=admin
DJANGO_SEKRET=secret12341234123412341234
TWO_FACTOR_OTP=3HTDZVFRYP4OPXHL

1
docker/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

59
docker/api/api.conf Normal file
View File

@ -0,0 +1,59 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server_tokens off;
upstream tacticalrmm {
server unix:///app/tacticalrmm.sock;
}
server {
listen 80;
#server_name ${API_HOST};
client_max_body_size 300M;
access_log /var/log/nginx/api-access.log;
error_log /var/log/nginx/api-error.log;
location /static/ {
root /app;
}
location /protected/ {
internal;
add_header "Access-Control-Allow-Origin" "https://${APP_HOST}";
alias /app/tacticalrmm/downloads/;
}
location /protectedlogs/ {
internal;
add_header "Access-Control-Allow-Origin" "https://${APP_HOST}";
alias /app/log/;
}
location / {
uwsgi_pass tacticalrmm;
include /etc/nginx/uwsgi_params;
uwsgi_read_timeout 9999s;
uwsgi_ignore_client_abort on;
}
}
}
daemon off;

29
docker/api/dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM tiangolo/uwsgi-nginx:python3.7
WORKDIR /app
ARG DJANGO_SEKRET
ARG POSTGRES_USER
ARG POSTGRES_PASS
ARG POSTGRES_HOST
ARG SALT_HOST
ARG SALT_USER
ARG SALT_PASS
ARG REDIS_HOST
ARG MESH_USER
ARG MESH_HOST
ARG APP_HOST
ARG API_HOST
ARG ADMIN_URL
ARG TWO_FACTOR_OTP
EXPOSE 80
RUN apt-get update && apt-get install -y gettext-base
COPY ./api/tacticalrmm .
RUN pip install --no-cache-dir -r requirements.txt
COPY ./docker/api/prestart.sh .
COPY ./docker/api/uwsgi.ini .
COPY ./docker/api/api.conf /app/api.conf.tmp
RUN envsubst '\$APP_HOST, \$API_HOST' < /app/api.conf.tmp > /app/nginx.conf && \
rm /app/api.conf.tmp
COPY ./docker/api/local_settings.py ./tacticalrmm/local_settings.py.tmp
RUN envsubst < /app/tacticalrmm/local_settings.py.tmp > /app/tacticalrmm/local_settings.py && rm /app/tacticalrmm/local_settings.py.tmp

5
docker/api/prestart.sh Executable file
View File

@ -0,0 +1,5 @@
#! /usr/bin/env bash
sleep 5
python manage.py migrate --no-input
python manage.py collectstatic --no-input

12
docker/api/uwsgi.ini Normal file
View File

@ -0,0 +1,12 @@
[uwsgi]
logto = /app/log/uwsgi.log
chdir = /app
wsgi-file = tacticalrmm/wsgi.py
master = true
processes = 4
threads = 2
socket = /app/tacticalrmm.sock
# clear environment on exit
vacuum = true
die-on-term = true

16
docker/app/app.conf Normal file
View File

@ -0,0 +1,16 @@
server {
listen 80;
#server_name ${APP_HOST};
charset utf-8;
location / {
root /usr/share/nginx/html;
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/app-error.log;
access_log /var/log/nginx/app-access.log;
}

17
docker/app/dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM node:12-alpine AS builder
ARG APP_HOST
ARG API_HOST
EXPOSE 80
WORKDIR /home/node
RUN apk add gettext
COPY ./web .
COPY ./docker/app/.env.local /home/.env.local.tmp
RUN envsubst '\$APP_HOST, \$API_HOST' < /home/.env.local.tmp > /home/node/.env.local && rm /home/.env.local.tmp
RUN npm install && npm run build
COPY ./docker/app/app.conf /home/node/app.conf.tmp
RUN envsubst '\$APP_HOST' < /home/node/app.conf.tmp > /home/node/app.conf
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY --from=builder /home/node/dist .
COPY --from=builder /home/node/app.conf /etc/nginx/conf.d/default.conf

170
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,170 @@
version: '3.5'
networks:
database:
redis:
proxy:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/24
services:
db:
image: "postgres"
restart: "always"
environment:
- POSTGRES_DB=tacticalrmm
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
networks:
- database
salt:
image: "saltstack/salt"
volumes:
- ./salt:/etc/salt/master.d
ports:
- "8123:8123"
- "4505:4505"
- "4506:4506"
meshcentral:
build:
context: ./meshcentral
args:
- MESH_HOST=${MESH_HOST}
networks:
- proxy
nginx-proxy:
build:
context: ./nginx-proxy
args:
- APP_HOST=${APP_HOST}
- API_HOST=${API_HOST}
- MESH_HOST=${MESH_HOST}
ports:
- "80:80"
- "443:443"
networks:
proxy:
ipv4_address: 172.20.0.20
depends_on:
- app
- api
- meshcentral
redis:
image: redis
networks:
- redis
app:
build:
context: ..
dockerfile: "./docker/app/dockerfile"
args:
- APP_HOST=${APP_HOST}
- API_HOST=${API_HOST}
networks:
- proxy
api:
build:
context: ..
dockerfile: "./docker/api/dockerfile"
args:
- DJANGO_SEKRET=${DJANGO_SEKRET}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
- POSTGRES_HOST=${POSTGRES_HOST}
- SALT_PASS=${SALT_PASS}
- SALT_USER=${SALT_USER}
- SALT_HOST=${SALT_HOST}
- REDIS_HOST=${REDIS_HOST}
- MESH_USER=${MESH_USER}
- MESH_HOST=${MESH_HOST}
- APP_HOST=${APP_HOST}
- API_HOST=${API_HOST}
- ADMIN_URL=${ADMIN_URL}
- TWO_FACTOR_OTP=${TWO_FACTOR_OTP}
networks:
- proxy
- database
- redis
depends_on:
- db
celery-service:
build:
context: ..
dockerfile: "./docker/api/dockerfile"
args:
- DJANGO_SEKRET=${DJANGO_SEKRET}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
- POSTGRES_HOST=${POSTGRES_HOST}
- SALT_PASS=${SALT_PASS}
- SALT_USER=${SALT_USER}
- SALT_HOST=${SALT_HOST}
- REDIS_HOST=${REDIS_HOST}
- MESH_USER=${MESH_USER}
- MESH_HOST=${MESH_HOST}
- APP_HOST=${APP_HOST}
- API_HOST=${API_HOST}
- ADMIN_URL=${ADMIN_URL}
- TWO_FACTOR_OTP=${TWO_FACTOR_OTP}
command: celery -A tacticalrmm worker -l info
networks:
- redis
- database
depends_on:
- db
- redis
celery-beat:
build:
context: ..
dockerfile: "./docker/api/dockerfile"
args:
- DJANGO_SEKRET=${DJANGO_SEKRET}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
- POSTGRES_HOST=${POSTGRES_HOST}
- SALT_PASS=${SALT_PASS}
- SALT_USER=${SALT_USER}
- SALT_HOST=${SALT_HOST}
- REDIS_HOST=${REDIS_HOST}
- MESH_USER=${MESH_USER}
- MESH_HOST=${MESH_HOST}
- APP_HOST=${APP_HOST}
- API_HOST=${API_HOST}
- ADMIN_URL=${ADMIN_URL}
- TWO_FACTOR_OTP=${TWO_FACTOR_OTP}
command: celery -A tacticalrmm beat -l info
networks:
- redis
- database
depends_on:
- db
- redis
celery-winupdate:
build:
context: ..
dockerfile: "./docker/api/dockerfile"
args:
- DJANGO_SEKRET=${DJANGO_SEKRET}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASS=${POSTGRES_PASS}
- POSTGRES_HOST=${POSTGRES_HOST}
- SALT_PASS=${SALT_PASS}
- SALT_USER=${SALT_USER}
- SALT_HOST=${SALT_HOST}
- REDIS_HOST=${REDIS_HOST}
- MESH_USER=${MESH_USER}
- MESH_HOST=${MESH_HOST}
- APP_HOST=${APP_HOST}
- API_HOST=${API_HOST}
- ADMIN_URL=${ADMIN_URL}
- TWO_FACTOR_OTP=${TWO_FACTOR_OTP}
command: celery -A tacticalrmm worker -Q wupdate
networks:
- redis
- database
depends_on:
- db
- redis

View File

@ -0,0 +1,31 @@
{
"settings": {
"Cert": "${MESH_HOST}",
"TLSOffload": "172.20.0.20",
"RedirPort": 80,
"WANonly": true,
"Minify": 1,
"Port": 443,
"AllowLoginToken": true,
"AllowFraming": true,
"_AgentPing": 60,
"AgentPong": 300,
"AllowHighQualityDesktop": true,
"MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 }
},
"domains": {
"": {
"Title": "Dev RMM",
"Title2": "DevRMM",
"NewAccounts": false,
"Footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>",
"GeoLocation": true,
"CertUrl": "https://172.20.0.20:443",
"httpheaders": {
"Strict-Transport-Security": "max-age=360000",
"_x-frame-options": "sameorigin",
"Content-Security-Policy": "default-src 'none'; script-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self'; media-src 'self'"
}
}
}
}

View File

@ -0,0 +1,11 @@
FROM node:stretch
WORKDIR /home/node/app
ARG MESH_HOST
RUN apt-get update && apt-get install -y gettext-base
RUN npm install meshcentral
COPY config.json ./meshcentral-data/config.json.tmp
RUN envsubst '\$MESH_HOST' < /home/node/app/meshcentral-data/config.json.tmp > /home/node/app/meshcentral-data/config.json && \
rm /home/node/app/meshcentral-data/config.json.tmp
CMD ["node", "./node_modules/meshcentral/meshcentral"]

View File

@ -0,0 +1,34 @@
server {
server_name ${API_HOST};
location / {
proxy_pass http://api;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
error_log /var/log/nginx/api-error.log;
access_log /var/log/nginx/api-access.log;
listen 443 ssl;
ssl_certificate /cert/fullchain.pem;
ssl_certificate_key /cert/privkey.pem;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
}
server {
listen 80;
server_name ${API_HOST};
return 301 https://$server_name$request_uri;
}

View File

@ -0,0 +1,35 @@
server {
server_name ${APP_HOST};
location / {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
error_log /var/log/nginx/app-error.log;
access_log /var/log/nginx/app-access.log;
listen 443 ssl;
ssl_certificate /cert/fullchain.pem;
ssl_certificate_key /cert/privkey.pem;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
}
server {
listen 80;
server_name ${APP_HOST};
return 301 https://$server_name$request_uri;
}

1
docker/nginx-proxy/cert/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.pem

View File

@ -0,0 +1,27 @@
FROM nginx
WORKDIR /etc/nginx/conf.d
ARG APP_HOST
ARG API_HOST
ARG MESH_HOST
EXPOSE 80
EXPOSE 443
#Remove default NGINX config
RUN rm /etc/nginx/conf.d/default.conf
#Copy APP config
COPY app.conf ./app.conf.tmp
RUN envsubst '\$APP_HOST' < /etc/nginx/conf.d/app.conf.tmp > /etc/nginx/conf.d/app.conf && rm /etc/nginx/conf.d/app.conf.tmp
#Copy API config
COPY api.conf ./api.conf.tmp
RUN envsubst '\$API_HOST' < /etc/nginx/conf.d/api.conf.tmp > /etc/nginx/conf.d/api.conf && rm /etc/nginx/conf.d/api.conf.tmp
#Copy Mesh config
COPY mesh.conf ./mesh.conf.tmp
RUN envsubst '\$MESH_HOST' < /etc/nginx/conf.d/mesh.conf.tmp > /etc/nginx/conf.d/mesh.conf && rm /etc/nginx/conf.d/mesh.conf.tmp
#Copy Certs
COPY ./cert/*.pem /cert/

View File

@ -0,0 +1,39 @@
server {
listen 80;
server_name ${MESH_HOST};
location / {
proxy_pass http://meshcentral;
proxy_http_version 1.1;
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;
}
}
server {
listen 443 ssl;
proxy_send_timeout 330s;
proxy_read_timeout 330s;
server_name ${MESH_HOST};
ssl_certificate /cert/fullchain.pem;
ssl_certificate_key /cert/privkey.pem;
ssl_session_cache shared:WEBSSL:10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://meshcentral:443;
proxy_http_version 1.1;
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;
}
}

52
docker/readme.md Normal file
View File

@ -0,0 +1,52 @@
# Docker Setup
- install docker and docker-compose
- Obtain wildcard cert or individual certs for each subdomain
## Optional - Generate certificates with certbot
Install Certbot
```sudo add-apt-repository ppa:certbot/certbot
sudo apt-get install certbot
```
Generate the wildcard certificate. Add the DNS entry for domain validation.
```sudo certbot certonly --manual -d *.example.com --agree-tos --no-bootstrap --manual-public-ip-logging-ok --preferred-challenges dns
```
Copy the fullchain.pem and privkey.pem to the cert directory.
## Run the environment with Docker
Change values in .env to match your environment
```cd docker
sudo docker-compose up -d
```
You may need to run this twice since some of the dependant containers won't be ready
## Create a super user
```sudo docker exec -it docker_api_1 python manage.py createsuperuser
```
## Setup 2FA authentication
Get the 2FA code with
```sudo docker exec -it docker_api_1 python manage.py generate_totp
```
Add the generated code to the .env file TWO_FACTOR_OTP in the docker folder
Rebuild the api container
```sudo docker-compose up -d --build api
```
Use the generated code and the username to generate a bar code for your authenticator app
```sudo docker exec -it docker_api_1 python manage.py generate_barcode [OTP_CODE] [username]
```

14
docker/salt/api.conf Normal file
View File

@ -0,0 +1,14 @@
timeout: 60
gather_job_timeout: 30
max_event_size: 30485760
external_auth:
pam:
saltapi:
- .*
- '@runner'
- '@wheel'
- '@jobs'
rest_cherrypy:
port: 8123
disable_ssl: True
max_request_body_size: 30485760

1
docker/salt/user.conf Normal file
View File

@ -0,0 +1 @@
{"user": "salt"}