Commit ca58b485 authored by Simon M. Haller-Seeber's avatar Simon M. Haller-Seeber
Browse files

Switch from certbot to traefik

parent 93440173
Loading
Loading
Loading
Loading
+8 −13
Original line number Diff line number Diff line
@@ -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) <br/>
*MYDOMAIN* is the FQDN for sharelatex and traefik (letsencrypt) <br/>
*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.
+137 −30
Original line number Diff line number Diff line
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 <a href=\"https://www.sharelatex.com\">ShareLaTeX</a> 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
+12 −4
Original line number Diff line number Diff line
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
+3 −0
Original line number Diff line number Diff line
#!/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
+22 −22
Original line number Diff line number Diff line
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;
Loading