diff --git a/docker/.env.example b/docker/.env.example index c0aa9d2a..3760ca6f 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -17,5 +17,13 @@ SALT_PASS=password ADMIN_URL=admin DJANGO_SEKRET=secret12341234123412341234 +DJANGO_DEBUG=False TWO_FACTOR_OTP=3HTDZVFRYP4OPXHL + +EMAIL_TLS=True +EMAIL_HOST=smtp.gmail.com +EMAIL_USER=example@gmail.com +EMAIL_PASS=changeme +EMAIL_PORT=587 +EMAIL_RECIPIENTS="example@gmail.com", diff --git a/docker/api/api.conf b/docker/api/api.conf index 807f9225..76a41cb2 100644 --- a/docker/api/api.conf +++ b/docker/api/api.conf @@ -46,6 +46,11 @@ http { alias /app/log/; } + location /protectedscripts/ { + internal; + add_header "Access-Control-Allow-Origin" "https://${APP_HOST}"; + alias /srv/salt/scripts/userdefined/; + } location / { uwsgi_pass tacticalrmm; diff --git a/docker/api/dockerfile b/docker/api/dockerfile index 34c5fe6f..21c13917 100644 --- a/docker/api/dockerfile +++ b/docker/api/dockerfile @@ -1,6 +1,9 @@ FROM tiangolo/uwsgi-nginx:python3.7 + WORKDIR /app + ARG DJANGO_SEKRET +ARG DJANGO_DEBUG ARG POSTGRES_USER ARG POSTGRES_PASS ARG POSTGRES_HOST @@ -14,16 +17,29 @@ ARG APP_HOST ARG API_HOST ARG ADMIN_URL ARG TWO_FACTOR_OTP +ARG EMAIL_TLS +ARG EMAIL_HOST +ARG EMAIL_USER +ARG EMAIL_PASS +ARG EMAIL_PORT +ARG EMAIL_RECIPIENTS EXPOSE 80 RUN apt-get update && apt-get install -y gettext-base -COPY ./api/tacticalrmm . +COPY ./api/tacticalrmm/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt +COPY ./api/tacticalrmm/ . 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 \ No newline at end of file +COPY ./docker/api/local_settings.py.keep ./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 + +COPY ./_modules /srv/salt/_modules +COPY ./scripts /srv/salt/scripts +RUN mkdir /srv/salt/scripts/userdefined && \ + chown -R 1000:nginx /srv/salt/scripts/userdefined && \ + chmod -R 771 /srv/salt/scripts/userdefined \ No newline at end of file diff --git a/docker/api/local_settings.py.keep b/docker/api/local_settings.py.keep new file mode 100644 index 00000000..7a2a461b --- /dev/null +++ b/docker/api/local_settings.py.keep @@ -0,0 +1,53 @@ +SECRET_KEY = '${DJANGO_SEKRET}' + +ALLOWED_HOSTS = ['${API_HOST}'] + +ADMIN_URL = "${ADMIN_URL}" + +CORS_ORIGIN_WHITELIST = ["https://${APP_HOST}",] + +DEBUG = ${DJANGO_DEBUG} + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'tacticalrmm', + 'USER': '${POSTGRES_USER}', + 'PASSWORD': '${POSTGRES_PASS}', + 'HOST': '${POSTGRES_HOST}', + 'PORT': '5432', + } +} + +REST_FRAMEWORK = { + 'DATETIME_FORMAT': "%b-%d-%Y - %H:%M", + + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'knox.auth.TokenAuthentication', + ), +} + +if not DEBUG: + REST_FRAMEWORK.update({ + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + ) + }) + +EMAIL_USE_TLS = ${EMAIL_TLS} +EMAIL_HOST = '${EMAIL_HOST}' +EMAIL_HOST_USER = '${EMAIL_USER}' +EMAIL_HOST_PASSWORD = '${EMAIL_PASS}' +EMAIL_PORT = ${EMAIL_PORT} +EMAIL_ALERT_RECIPIENTS = [${EMAIL_RECIPIENTS}] + +SALT_USERNAME = "${SALT_USER}" +SALT_PASSWORD = "${SALT_PASS}" +MESH_USERNAME = "${MESH_USER}" +MESH_SITE = "https://${MESH_HOST}" +REDIS_HOST = "${REDIS_HOST}" +SALT_HOST = "${SALT_HOST}" +TWO_FACTOR_OTP = "${TWO_FACTOR_OTP}" \ No newline at end of file diff --git a/docker/app/.env.local.keep b/docker/app/.env.local.keep new file mode 100644 index 00000000..fdf07294 --- /dev/null +++ b/docker/app/.env.local.keep @@ -0,0 +1,2 @@ +VUE_APP_PROD_URL = "https://${API_HOST}" +VUE_APP_DEV_URL = "https://${API_HOST}" \ No newline at end of file diff --git a/docker/app/dockerfile b/docker/app/dockerfile index a867cc4d..1f7bd3dc 100644 --- a/docker/app/dockerfile +++ b/docker/app/dockerfile @@ -5,8 +5,8 @@ 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 +COPY ./docker/app/.env.local.keep /home/.env.local.tmp +RUN envsubst '\$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 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 32c72037..769bd470 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,5 +1,6 @@ version: '3.5' +# Userdefined Networks networks: database: redis: @@ -10,7 +11,14 @@ networks: config: - subnet: 172.20.0.0/24 +# Docker managed persistent volumes +volumes: + # Volume for userdefined scripts + scripts: + services: + + # Postgres Database for API service db: image: "postgres" restart: "always" @@ -20,14 +28,25 @@ services: - POSTGRES_PASS=${POSTGRES_PASS} networks: - database + + # Salt Master and API salt: - image: "saltstack/salt" - volumes: - - ./salt:/etc/salt/master.d + build: + context: .. + dockerfile: ./docker/salt/dockerfile + args: + - SALT_USER=${SALT_USER} + - SALT_PASS=${SALT_PASS} ports: - "8123:8123" - "4505:4505" - "4506:4506" + volumes: + - scripts:/srv:ro + networks: + - proxy + + # MeshCentral Container meshcentral: build: context: ./meshcentral @@ -35,6 +54,10 @@ services: - MESH_HOST=${MESH_HOST} networks: - proxy + depends_on: + - nginx-proxy + + # Nginx Container Reverse Proxy that handles all http/https traffic nginx-proxy: build: context: ./nginx-proxy @@ -48,14 +71,14 @@ services: networks: proxy: ipv4_address: 172.20.0.20 - depends_on: - - app - - api - - meshcentral + + # Redis Container for Celery tasks redis: image: redis networks: - redis + + # Container that hosts Vue frontend app: build: context: .. @@ -65,12 +88,15 @@ services: - API_HOST=${API_HOST} networks: - proxy + + # Container for Django backend api: build: context: .. dockerfile: "./docker/api/dockerfile" args: - DJANGO_SEKRET=${DJANGO_SEKRET} + - DJANGO_DEBUG=${DJANGO_DEBUG} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASS=${POSTGRES_PASS} - POSTGRES_HOST=${POSTGRES_HOST} @@ -84,18 +110,29 @@ services: - API_HOST=${API_HOST} - ADMIN_URL=${ADMIN_URL} - TWO_FACTOR_OTP=${TWO_FACTOR_OTP} + - EMAIL_TLS=${EMAIL_TLS} + - EMAIL_HOST=${EMAIL_HOST} + - EMAIL_USER=${EMAIL_USER} + - EMAIL_PASS=${EMAIL_PASS} + - EMAIL_PORT=${EMAIL_PORT} + - EMAIL_RECIPIENTS=${EMAIL_RECIPIENTS} networks: - proxy - database - redis + volumes: + - scripts:/srv depends_on: - db + + # Container for Celery worker service celery-service: build: context: .. dockerfile: "./docker/api/dockerfile" args: - DJANGO_SEKRET=${DJANGO_SEKRET} + - DJANGO_DEBUG=${DJANGO_DEBUG} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASS=${POSTGRES_PASS} - POSTGRES_HOST=${POSTGRES_HOST} @@ -109,6 +146,12 @@ services: - API_HOST=${API_HOST} - ADMIN_URL=${ADMIN_URL} - TWO_FACTOR_OTP=${TWO_FACTOR_OTP} + - EMAIL_TLS=${EMAIL_TLS} + - EMAIL_HOST=${EMAIL_HOST} + - EMAIL_USER=${EMAIL_USER} + - EMAIL_PASS=${EMAIL_PASS} + - EMAIL_PORT=${EMAIL_PORT} + - EMAIL_RECIPIENTS=${EMAIL_RECIPIENTS} command: celery -A tacticalrmm worker -l info networks: - redis @@ -116,12 +159,15 @@ services: depends_on: - db - redis + + # Container for Celery beat service celery-beat: build: context: .. dockerfile: "./docker/api/dockerfile" args: - DJANGO_SEKRET=${DJANGO_SEKRET} + - DJANGO_DEBUG=${DJANGO_DEBUG} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASS=${POSTGRES_PASS} - POSTGRES_HOST=${POSTGRES_HOST} @@ -135,6 +181,12 @@ services: - API_HOST=${API_HOST} - ADMIN_URL=${ADMIN_URL} - TWO_FACTOR_OTP=${TWO_FACTOR_OTP} + - EMAIL_TLS=${EMAIL_TLS} + - EMAIL_HOST=${EMAIL_HOST} + - EMAIL_USER=${EMAIL_USER} + - EMAIL_PASS=${EMAIL_PASS} + - EMAIL_PORT=${EMAIL_PORT} + - EMAIL_RECIPIENTS=${EMAIL_RECIPIENTS} command: celery -A tacticalrmm beat -l info networks: - redis @@ -142,12 +194,15 @@ services: depends_on: - db - redis + + # Container for Celery Winupdate tasks celery-winupdate: build: context: .. dockerfile: "./docker/api/dockerfile" args: - DJANGO_SEKRET=${DJANGO_SEKRET} + - DJANGO_DEBUG=${DJANGO_DEBUG} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASS=${POSTGRES_PASS} - POSTGRES_HOST=${POSTGRES_HOST} @@ -161,6 +216,12 @@ services: - API_HOST=${API_HOST} - ADMIN_URL=${ADMIN_URL} - TWO_FACTOR_OTP=${TWO_FACTOR_OTP} + - EMAIL_TLS=${EMAIL_TLS} + - EMAIL_HOST=${EMAIL_HOST} + - EMAIL_USER=${EMAIL_USER} + - EMAIL_PASS=${EMAIL_PASS} + - EMAIL_PORT=${EMAIL_PORT} + - EMAIL_RECIPIENTS=${EMAIL_RECIPIENTS} command: celery -A tacticalrmm worker -Q wupdate networks: - redis diff --git a/docker/nginx-proxy/api.conf b/docker/nginx-proxy/api.conf index c0a6629b..3d596b85 100644 --- a/docker/nginx-proxy/api.conf +++ b/docker/nginx-proxy/api.conf @@ -1,8 +1,13 @@ server { + resolver 127.0.0.11 valid=30s; + server_name ${API_HOST}; location / { - proxy_pass http://api; + #Using variable to disable start checks + set $api http://api; + + proxy_pass $api; proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; @@ -19,6 +24,8 @@ server { error_log /var/log/nginx/api-error.log; access_log /var/log/nginx/api-access.log; + client_max_body_size 300M; + listen 443 ssl; ssl_certificate /cert/fullchain.pem; ssl_certificate_key /cert/privkey.pem; diff --git a/docker/nginx-proxy/app.conf b/docker/nginx-proxy/app.conf index c871fbfb..124a6f71 100644 --- a/docker/nginx-proxy/app.conf +++ b/docker/nginx-proxy/app.conf @@ -1,8 +1,13 @@ server { + resolver 127.0.0.11 valid=30s; + server_name ${APP_HOST}; location / { - proxy_pass http://app; + #Using variable to disable start checks + set $app http://app; + + proxy_pass $app; proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; diff --git a/docker/nginx-proxy/mesh.conf b/docker/nginx-proxy/mesh.conf index 67dc0ee5..2e5c5769 100644 --- a/docker/nginx-proxy/mesh.conf +++ b/docker/nginx-proxy/mesh.conf @@ -1,10 +1,14 @@ server { + resolver 127.0.0.11 valid=30s; listen 80; server_name ${MESH_HOST}; location / { - proxy_pass http://meshcentral; + #Using variable to disable start checks + set $meshcentral http://meshcentral; + + proxy_pass $meshcentral; proxy_http_version 1.1; proxy_set_header X-Forwarded-Host $host:$server_port; @@ -14,6 +18,7 @@ server { } server { + resolver 127.0.0.11 valid=30s; listen 443 ssl; proxy_send_timeout 330s; @@ -26,7 +31,10 @@ server { ssl_prefer_server_ciphers on; location / { - proxy_pass http://meshcentral:443; + #Using variable to disable start checks + set $meshcentral http://meshcentral:443; + + proxy_pass $meshcentral; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; diff --git a/docker/readme.md b/docker/readme.md index 06802685..133f7148 100644 --- a/docker/readme.md +++ b/docker/readme.md @@ -3,7 +3,7 @@ - install docker and docker-compose - Obtain wildcard cert or individual certs for each subdomain -## Optional - Generate certificates with certbot +## Generate certificates with certbot Install Certbot @@ -13,13 +13,19 @@ 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 +``` +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. +## Configure DNS and Firewall + +You will need to add DNS entries so that the three subdomains resolve to the IP of the docker host. There is a reverse proxy running that will route the hostnames to the correct container. On the host, you will need to ensure the firewall is open on tcp ports 80, 443, 8123, 4505, 4506. + ## Run the environment with Docker -Change values in .env to match your environment +Copy the .env.example to .env then +change values in .env to match your environment ``` cd docker @@ -55,3 +61,15 @@ Use the generated code and the username to generate a bar code for your authenti ``` sudo docker exec -it docker_api_1 python manage.py generate_barcode [OTP_CODE] [username] ``` + +## Connect to a container instance shell + +run `docker ps` to get the name of the running container instance. + +Then use the name in the below command. It will use the api container instance as an example + +``` +sudo docker exec -it docker_api_1 /bin/bash +``` + +If /bin/bash doesn't work then /bin/sh might need to be used. \ No newline at end of file diff --git a/docker/salt/api.conf b/docker/salt/api.conf index 9342a309..03f213ee 100644 --- a/docker/salt/api.conf +++ b/docker/salt/api.conf @@ -3,7 +3,7 @@ gather_job_timeout: 30 max_event_size: 30485760 external_auth: pam: - saltapi: + ${SALT_USER}: - .* - '@runner' - '@wheel' @@ -11,4 +11,4 @@ external_auth: rest_cherrypy: port: 8123 disable_ssl: True - max_request_body_size: 30485760 \ No newline at end of file + max_request_body_size: 30485760 diff --git a/docker/salt/dockerfile b/docker/salt/dockerfile new file mode 100644 index 00000000..ffb669cb --- /dev/null +++ b/docker/salt/dockerfile @@ -0,0 +1,12 @@ +FROM saltstack/salt + +ARG SALT_USER +ARG SALT_PASS + +RUN adduser --no-create-home --disabled-password --gecos "" ${SALT_USER} && \ + echo "${SALT_USER}:${SALT_PASS}" | chpasswd +RUN apk add gettext + +COPY ./docker/salt/api.conf /etc/salt/master.d/api.conf.tmp +RUN envsubst '\$SALT_USER' < /etc/salt/master.d/api.conf.tmp > /etc/salt/master.d/api.conf && \ + rm /etc/salt/master.d/api.conf.tmp diff --git a/docker/salt/user.conf b/docker/salt/user.conf deleted file mode 100644 index dac049e7..00000000 --- a/docker/salt/user.conf +++ /dev/null @@ -1 +0,0 @@ -{"user": "salt"} \ No newline at end of file