diff --git a/README.md b/README.md index be80c1e..d8dfa42 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Sharelatex/Overleaf uses the email address to identify users: If you change the in the mongo db. ``` -docker exec -it mongo +docker exec -it mongo /bin/bash mongo use sharelatex db.users.find({email:"EMAIL"}).pretty() @@ -56,7 +56,8 @@ MYDATA=/data - sharelatex: all projects, tmp files, user files templates and ... - letsencrypt: https certificates -*MYDOMAIN* is the FQDN for sharelatex and certbot (letsencrypt)
+*MYDOMAIN* is the FQDN for sharelatex and traefik (letsencrypt)
+*MYDOMAIN*:8443 Traefik Dashboard - Login uses traefik/user.htpasswd : user:admin pass:adminPass change this (e.g. generate a password with htpasswd) *MYMAIL* is the admin mailaddress ``` @@ -125,21 +126,15 @@ make ``` to generate the ldap-overleaf-sl docker image. -Then start docker containers: +Then start docker containers (with loadbalancer): ``` -docker-compose up -d +export NUMINSTANCES=1 +docker-compose up -d --scale sharelaatex=NUMINSTANCES ``` *Known Issue:* -During the first startup the certbot image will get an initial certificate - if that -happens not in a very timely manner sharelatex will fail to start (due to the missing certificates -nginx crashes). Solution: wait 10 seconds and restart the sharelatex container. +- Works up to sharelatex 2.3.1. After that at least issue #5 is introduced. + -``` -docker stop ldap-overleaf-sl -docker-compose up -d -``` -After the inital startup and certificate configuration you can reconfigure the -docker-compose.yml that port 80 points to the Sharelatex/Overleaf instance. diff --git a/docker-compose.yml b/docker-compose.yml index 8a86d91..507867e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,27 +1,108 @@ version: '2.2' services: + traefik: + image: traefik:latest + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - web + ports: + - 80:80 + - 443:443 + - 8443:8443 + # - 8080:8080 + # - 27017:27017 + volumes: + - ${MYDATA}/letsencrypt:/letsencrypt + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik/dynamic_conf.yml:/dynamic_conf.yml + - ./traefik/users.htpasswd:/users.htpasswd + + command: + - "--api=true" + - "--api.dashboard=true" + #- "--api.insecure=true" # provides the dashboard on http://IPADRESS:8080 + - "--providers.docker=true" + - "--ping" + - "--providers.docker.network=web" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:80" + - "--entrypoints.web-secure.address=:443" + - "--entrypoints.web-admin.address=:8443" + - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge=true" + - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge.entrypoint=web" + - "--certificatesresolvers.myhttpchallenge.acme.email=${MYMAIL}" + - "--certificatesresolvers.myhttpchallenge.acme.storage=/letsencrypt/acme.json" + - "--entrypoints.mongo.address=:27017" + #- --certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-v02.api.letsencrypt.org/directory + labels: + - "traefik.enable=true" + # To Fix enable dashboard on port 8443 + - "traefik.http.routers.dashboard.entrypoints=web-admin" + - "traefik.http.routers.dashboard.rule=Host(`${MYDOMAIN}`)" + # - "traefik.http.routers.dashboard.rule=Host(`traefik.${MYDOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + - "traefik.http.routers.dashboard.tls=true" + - "traefik.http.routers.dashboard.middlewares=auth" + - "traefik.http.middlewares.auth.basicauth.usersfile=/users.htpasswd" + - "traefik.http.routers.dashboard.service=api@internal" + + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "1" + sharelatex: restart: always image: ldap-overleaf-sl:latest - container_name: ldap-overleaf-sl depends_on: mongo: condition: service_healthy redis: condition: service_healthy - simple-certbot: + traefik: condition: service_started + #simple-certbot: + # condition: service_started privileged: false - ports: - - 443:443 + networks: + - web + expose: + - 80 + - 443 links: - mongo - redis - - simple-certbot + #- simple-certbot volumes: - ${MYDATA}/sharelatex:/var/lib/sharelatex - - ${MYDATA}/letsencrypt:/etc/letsencrypt - - ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain + - ${MYDATA}/letsencrypt:/etc/letsencrypt:ro + # - ${MYDATA}/letsencrypt/live/${MYDOMAIN}/:/etc/letsencrypt/certs/domain + labels: + - "traefik.enable=true" + - "traefik.http.routers.tex.entrypoints=web" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + - "traefik.http.routers.sharel.middlewares=redirect-to-https@docker" + - "traefik.http.routers.sharel-secured.rule=Host(`${MYDOMAIN}`)" + - "traefik.http.routers.sharel-secured.tls=true" + - "traefik.http.routers.sharel-secured.tls.certresolver=myhttpchallenge" + - "traefik.http.routers.sharel-secured.entrypoints=web-secure" + - "traefik.http.routers.proxy-https.entrypoints=web-secure" + - "traefik.http.routers.proxy-https.rule=Host(`${MYDOMAIN}`)" + - "traefik.http.services.sharel.loadbalancer.server.port=80" + - "traefik.http.services.sharel.loadbalancer.server.scheme=http" + # ToDo - internally connect via https: reuse the certifiacte from traefik (acme.json) + #- "traefik.http.services.sharel.loadbalancer.server.port=443" + #- "traefik.http.services.sharel.loadbalancer.server.scheme=https" + - "traefik.http.services.sharel.loadbalancer.sticky.cookie=true" + - "traefik.http.services.sharel.loadbalancer.sticky.cookie.name=io" + - "traefik.http.services.sharel.loadbalancer.sticky.cookie.httponly=true" + - "traefik.http.services.sharel.loadbalancer.sticky.cookie.secure=true" + - "traefik.http.services.sharel.loadbalancer.sticky.cookie.samesite=io" + environment: SHARELATEX_APP_NAME: Overleaf SHARELATEX_MONGO_URL: mongodb://mongo/sharelatex @@ -32,8 +113,6 @@ services: SHARELATEX_LEFT_FOOTER: '[{"text": "Powered by ShareLaTeX 2016"} ]' SHARELATEX_RIGHT_FOOTER: '[{"text": "LDAP Overleaf (beta)"} ]' SHARELATEX_EMAIL_FROM_ADDRESS: "noreply@${MYDOMAIN}" - # SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID: - # SHARELATEX_EMAIL_AWS_SES_SECRET_KEY: SHARELATEX_EMAIL_SMTP_HOST: smtp.${MYDOMAIN} SHARELATEX_EMAIL_SMTP_PORT: 587 SHARELATEX_EMAIL_SMTP_SECURE: 'false' @@ -43,18 +122,31 @@ services: # SHARELATEX_EMAIL_SMTP_IGNORE_TLS: false SHARELATEX_CUSTOM_EMAIL_FOOTER: "This system is run by ${MYDOMAIN} - please contact ${MYMAIL} if you experience any issues." + # make public links accessible w/o login (link sharing issue) + # https://github.com/overleaf/docker-image/issues/66 + # https://github.com/overleaf/overleaf/issues/628 + # https://github.com/overleaf/web/issues/367 + # Fixed in 2.0.2 (Release date: 2019-11-26) + SHARELATEX_ALLOW_PUBLIC_ACCESS: 'true' + SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true' + + SHARELATEX_SECURE_COOKIE: 'true' + SHARELATEX_BEHIND_PROXY: 'true' + LDAP_SERVER: ldaps://LDAPSERVER:636 LDAP_BASE: ou=people,dc=DOMAIN,dc=TLD LDAP_BINDDN: ou=someunit,ou=people,dc=DOMAIN,dc=TLS + # By default tries to bind directly with the ldap user - this user has to be in the LDAP GROUP + # LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' LDAP_GROUP_FILTER: '(memberof=cn=GROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' # If user is in ADMIN_GROUP on user creation (first login) isAdmin is set to true. # Admin Users can invite external (non ldap) users. This feature makes only sense # when ALLOW_EMAIL_LOGIN is set to 'true'. Additionally admins can send # system wide messages. - #LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' - ALLOW_EMAIL_LOGIN: 'false' + LDAP_ADMIN_GROUP_FILTER: '(memberof=cn=ADMINGROUPNAME,ou=groups,dc=DOMAIN,dc=TLD)' + ALLOW_EMAIL_LOGIN: 'true' # All users in the LDAP_GROUP_FILTER are loaded from the ldap server into contacts. # This LDAP search happens without bind. If you want this and your LDAP needs a bind you can @@ -76,7 +168,7 @@ services: restart: always image: mongo container_name: mongo - ports: + expose: - 27017 volumes: - ${MYDATA}/mongo_data:/data/db @@ -85,6 +177,14 @@ services: interval: 10s timeout: 10s retries: 5 + labels: + - "traefik.enable=true" + - "traefik.tcp.routers.mongodb.rule=HostSNI(`*`)" + - "traefik.tcp.services.mongodb.loadbalancer.server.port=27017" + - "traefik.tcp.routers.mongodb.tls=true" + - "traefik.tcp.routers.mongodb.entrypoints=mongo" + networks: + - web redis: restart: always @@ -98,7 +198,7 @@ services: sysctls: - net.core.somaxconn=65535 # - vm.overcommit_memory=1 - ports: + expose: - 6379 volumes: - ${MYDATA}/redis_data:/data @@ -107,23 +207,30 @@ services: interval: 10s timeout: 5s retries: 5 + networks: + - web - simple-certbot: - restart: always - image: certbot/certbot - container_name: simple-certbot - ports: - - 80:80 - volumes: - - ${MYDATA}/letsencrypt:/etc/letsencrypt - # a bit hacky but this docker image uses very little disk-space - # best practices for ssl and nginx are set in the ldap-overleaf-sl Dockerfile - entrypoint: - - "/bin/sh" - - -c - - | - trap exit TERM;\ - certbot certonly --standalone -d ${MYDOMAIN} --agree-tos -m ${MYMAIL} -n ; \ - while :; do certbot renew; sleep 240h & wait $${!}; done; +# simple-certbot: +# restart: always +# image: certbot/certbot +# container_name: simple-certbot +# ports: +# - 80:80 +# volumes: +# - ${MYDATA}/letsencrypt:/etc/letsencrypt +# # a bit hacky but this docker image uses very little disk-space +# # best practices for ssl and nginx are set in the ldap-overleaf-sl Dockerfile +# entrypoint: +# - "/bin/sh" +# - -c +# - | +# trap exit TERM;\ +# certbot certonly --standalone -d ${MYDOMAIN} --agree-tos -m ${MYMAIL} -n ; \ +# while :; do certbot renew; sleep 240h & wait $${!}; done; +# + +networks: + web: + external: true diff --git a/ldap-overleaf-sl/Dockerfile b/ldap-overleaf-sl/Dockerfile index 79971a4..a6c958c 100644 --- a/ldap-overleaf-sl/Dockerfile +++ b/ldap-overleaf-sl/Dockerfile @@ -1,4 +1,4 @@ -FROM sharelatex/sharelatex:latest +FROM sharelatex/sharelatex:2.3.1 LABEL maintainer="Simon Haller-Seeber" LABEL version="0.1" @@ -7,14 +7,15 @@ ARG collab_text ARG login_text # set workdir (might solve issue #2 - see https://stackoverflow.com/questions/57534295/) -WORKDIR /var/www/sharelatex +WORKDIR /var/www/sharelatex/web # install latest npm RUN npm install -g npm # clean cache (might solve issue #2) -RUN npm cache clean --force +#RUN npm cache clean --force RUN npm install ldapts-search RUN npm install ldapts +#RUN npm install bcrypt@5.0.0 # This variant of updateing texlive does not work #RUN bash -c tlmgr install scheme-full @@ -48,7 +49,7 @@ COPY sharelatex/admin-index.pug /var/www/sharelatex/web/app/views/admin/index.p RUN rm /var/www/sharelatex/web/app/views/admin/register.pug ### To remove comments entirly (bug https://github.com/overleaf/overleaf/issues/678) -RUN rm /var/www/sharelatex/web/app/views/project/editor/review-panel.pug +#RUN rm /var/www/sharelatex/web/app/views/project/editor/review-panel.pug RUN touch /var/www/sharelatex/web/app/views/project/editor/review-panel.pug ### Nginx and Certificates @@ -63,3 +64,10 @@ RUN wget https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbo # reload nginx via cron for reneweing https certificates automatically COPY nginx/nginx-reload.sh /etc/cron.weekly/ RUN chmod 0744 /etc/cron.weekly/nginx-reload.sh + +## extract certificates from acme.json? +# COPY nginx/nginx-cert.sh /etc/cron.weekly/ +# RUN chmod 0744 /etc/cron.weekly/nginx-cert.sh +# RUN echo "/usr/cron.weekly/nginx-cert.sh 2>&1 > /dev/null" > /etc/rc.local +# RUN chmod 0744 /etc/rc.local + diff --git a/ldap-overleaf-sl/nginx/nginx-cert.sh b/ldap-overleaf-sl/nginx/nginx-cert.sh new file mode 100644 index 0000000..d185c59 --- /dev/null +++ b/ldap-overleaf-sl/nginx/nginx-cert.sh @@ -0,0 +1,3 @@ +#!/bin/bash +less /etc/letsencrypt/acme.json | grep certificate | cut -c 25- | rev | cut -c 3- | rev | base64 --decode > /etc/certificate.crt +less /etc/letsencrypt/acme.json | grep key | cut -c 17- | rev | cut -c 3- | rev | base64 --decode > /etc/key.crt diff --git a/ldap-overleaf-sl/nginx/sharelatex.conf b/ldap-overleaf-sl/nginx/sharelatex.conf index f6f5bb5..663a0ec 100644 --- a/ldap-overleaf-sl/nginx/sharelatex.conf +++ b/ldap-overleaf-sl/nginx/sharelatex.conf @@ -1,31 +1,31 @@ server { listen 80; server_name _; # Catch all, see http://nginx.org/en/docs/http/server_names.html - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - location / { - return 301 https://$host$request_uri; - } -} +# location / { +# return 301 https://$host$request_uri; +# } +#} +# +# +#server { +# +# listen 443 ssl default_server; +# listen [::]:443 ssl default_server; +# server_name _; # Catch all - -server { - - listen 443 ssl default_server; - listen [::]:443 ssl default_server; - server_name _; # Catch all + add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;"; + server_tokens off; + add_header X-Frame-Options SAMEORIGIN; + add_header X-Content-Type-Options nosniff; set $static_path /var/www/sharelatex/web/public; - ssl_certificate /etc/letsencrypt/certs/domain/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/certs/domain/privkey.pem; - include /etc/nginx/options-ssl-nginx.conf; - ssl_dhparam /etc/nginx/ssl-dhparams.pem; - - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - +# ssl_certificate /etc/certificate.crt; +# ssl_certificate_key /etc/key.crt; +# ssl_certificate /etc/letsencrypt/certs/domain/fullchain.pem; +# ssl_certificate_key /etc/letsencrypt/certs/domain/privkey.pem; +# include /etc/nginx/options-ssl-nginx.conf; +# ssl_dhparam /etc/nginx/ssl-dhparams.pem; +# location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; diff --git a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js index e5213e9..6a38ba9 100644 --- a/ldap-overleaf-sl/sharelatex/AuthenticationManager.js +++ b/ldap-overleaf-sl/sharelatex/AuthenticationManager.js @@ -57,6 +57,8 @@ const AuthenticationManager = { //console.log("Creating User:" + JSON.stringify(query)) //create random pass for local userdb, does not get checked for ldap users during login let pass = require("crypto").randomBytes(32).toString("hex") + console.log("Creating User:" + JSON.stringify(query) + "Random Pass" + pass) + const userRegHand = require('../User/UserRegistrationHandler.js') userRegHand.registerNewUser({ email: mail, @@ -179,7 +181,7 @@ const AuthenticationManager = { checkRounds(user, hashedPassword, password, callback) { // Temporarily disable this function, TODO: re-enable this - //callback() + return callback() if (Settings.security.disableBcryptRoundsUpgrades) { return callback() } @@ -212,7 +214,7 @@ const AuthenticationManager = { } db.users.update( { - _id: ObjectId(userId._id.toString()) + _id: ObjectId(userId.toString()) }, { $set: { diff --git a/traefik/dynamic_conf.yml b/traefik/dynamic_conf.yml new file mode 100644 index 0000000..5e46645 --- /dev/null +++ b/traefik/dynamic_conf.yml @@ -0,0 +1,33 @@ +tls: + options: + default: + minVersion: VersionTLS12 + cipherSuites: + - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + curvePreferences: + - CurveP521 + - CurveP384 + sniStrict: true + +http: + middlewares: + secHeaders: + headers: + browserXssFilter: true + contentTypeNosniff: true + frameDeny: true + sslRedirect: true + #HSTS Configuration + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 31536000 + customFrameOptionsValue: "SAMEORIGIN" + + https-redirect: + redirectScheme: + scheme: https diff --git a/traefik/users.htpasswd b/traefik/users.htpasswd new file mode 100644 index 0000000..3dd4180 --- /dev/null +++ b/traefik/users.htpasswd @@ -0,0 +1 @@ +admin:$apr1$7xGHLKlO$Mx2DNcWfqiHfH1WLg51ul.