diff --git a/README.md b/README.md index 959a094..0217cfc 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,24 @@ Make sure to support the developers by buying the choosen subscription for your ## How to setup (api) (optional) ## This is _optional_. You can simply use the default instance of this API (host is noted inside the `setup.py` script) and profit from "automatic" updates. +## API Only: Using Apache Just transfer the `www` files inside a public accessible root-folder on your _dedicated_ Apache webserver (really everthing with PHP support works). Also make sure your instance has a valid SSL-certificate (Let's encrypt is enough), otherwise it may won't work. An example Apache install process can be found [here](docs/apache/install.md). If you want to test your instance, just open the public accessible URI in your browser and append `/healthz` to it - if you see some JSON with the text, then everything worked! +### API Only: Using Nginx +Just transfer the `www` files inside a public accessible root-folder on your _dedicated_ Nginx webserver (really everthing with PHP support works). Also make sure your instance has a valid SSL-certificate (Let's encrypt is enough), otherwise it may won't work. +See the documentation in [Nginx Install](docs/nginx/install.md). + +### API Only: Using Docker +See the documentation in [Docker Install](docs/docker/api-only-install.md). + +### Fully Patched Pritunl: Using Docker +This api has also its own docker image. Take a look into the `docker` folder and enjoy! + +See the documentation in [Patched Pritunl Docker Install](docs/docker/pritunl-patched-install.md). + ### Nett2Know ### * This modification will also block any communication to the Pritunl servers - so no calling home :) * SSO will not work with this api version! As Pritunls own authentication servers handle the whole SSO stuff, track instance ids and verify users, I won't implement this part for privacy concerns (and also this would need to be securly implemented and a database). -* This api has also its own docker image. Take a look into the `docker` folder and enjoy! Have fun with your new premium/enterprise/ultimate Pritunl instance! diff --git a/docker/Dockerfile b/docker/Dockerfile index 03a50e7..9a85f97 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,9 @@ FROM goofball222/pritunl:latest +ARG API_SERVER_DOMAIN +ENV API_SERVER_DOMAIN $API_SERVER_DOMAIN + # Yes, you will need to copy it over into the build context... COPY setup.py . -RUN chmod +x setup.py; python3 -u setup.py --install; rm setup.py \ No newline at end of file +RUN chmod +x setup.py; python3 -u setup.py --install --api-server ${API_SERVER_DOMAIN:-}; rm setup.py \ No newline at end of file diff --git a/docker/api-only/conf.d/pritunl-fake-api.conf b/docker/api-only/conf.d/pritunl-fake-api.conf new file mode 100644 index 0000000..9423ed8 --- /dev/null +++ b/docker/api-only/conf.d/pritunl-fake-api.conf @@ -0,0 +1,40 @@ + # Pritunl Fake API Server definition + server { + listen [::]:80 default_server; + listen 80 default_server; + server_name _; + + sendfile off; + tcp_nodelay on; + absolute_redirect off; + + root /var/www/html; + index index.php index.html; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to index.php + try_files $uri $uri/ /index.php?path=$uri&$args; + } + + # Pass the PHP scripts to PHP-FPM listening on php-fpm.sock + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_index index.php; + include fastcgi_params; + } + + location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ { + expires 5d; + } + + # Deny access to . files, for security + location ~ /\. { + log_not_found off; + deny all; + } + } \ No newline at end of file diff --git a/docker/api-only/docker-compose.yml b/docker/api-only/docker-compose.yml new file mode 100644 index 0000000..5cfcc3b --- /dev/null +++ b/docker/api-only/docker-compose.yml @@ -0,0 +1,98 @@ +# Runs this API, either on port 80 or behind Traefik, either on docker swarm or single daemon. +# Choose the right configuration for you and comment out the other. +# Read the comments carefully. +# +# +# In case you run behind Traefik, you need to setup the traefik router HOST +# You need correctly setup traefik and docker network (here called proxy_external) +# +# /!\ /!\ Make sure the mount volumes match correctly. /!\ /!\ +# +# The first volume is the path to the www folder from the root of this repo. +# The path shall be a full path, or be next to this docker-compose.yml file. +# No parent folder navigation like `../../../` is allowed by docker. +# +# -> Easy solution: +# Once you have cloned this repo, you shall move this docker-compose.yml file to the root of the repo. +# +# The second volume is the path to the nginx server config file. +# This needs the commited nginx server config (or your own adapted version) to work properly. +# See the file `/docker/api-only/conf.d/pritunl-fake-api.conf` for more details. + +version: '3.7' +services: + web: + image: trafex/php-nginx + volumes: + - "./www:/var/www/html:ro" + - "./docker/api-only/conf.d/pritunl-fake-api.conf:/etc/nginx/conf.d/pritunl-fake-api.conf" + ################################################################# + ### If you run behind Traefik COMMENT OUT the following lines ### + ### BEGIN TRAEFIK_BLOCK ### + ports: + - "80:8080" + ################################################################# + + ################################################################# + + ### BEGIN SINGLE_DAEMON_BLOCK ### + + ### If you run behind on Docker Single Daemon (NOT Swarm) uncomment the following lines ### + # networks: + # - default + # - proxy_external + # labels: + # - "traefik.enable=true" + # - "traefik.docker.network=proxy_external" + # - "traefik.tags=proxy_external" + # ### Services + # ## API + # - "traefik.http.services.pritunl-api.loadbalancer.server.port=8080" + # ### Routers + # - "traefik.http.routers.pritunl-api.entrypoints=https" + # - "traefik.http.routers.pritunl-api.rule=Host(`mypritunlfakeapi.example.com`)" + # - "traefik.http.routers.pritunl-api.service=pritunl-api" + # - "traefik.http.routers.pritunl-api.tls=true" + # - "traefik.http.routers.pritunl-api.tls.certresolver=http" +# networks: +# proxy_external: +# external: true +# name: proxy_external + + ### END SINGLE_DAEMON_BLOCK ### + + + ### BEGIN SWARM_BLOCK ### + + ### If you run on Docker Swarm uncomment the following lines ### + # networks: + # - default + # - proxy_external + # labels: + # - "traefik.enable=true" + # deploy: + # labels: + # - "traefik.enable=true" + # - "traefik.docker.network=proxy_external" + # - "traefik.tags=proxy_external" + # ### Services + # ## API + # - "traefik.http.services.pritunl-api.loadbalancer.server.port=8080" + # ### Routers + # - "traefik.http.routers.pritunl-api.entrypoints=https" + # - "traefik.http.routers.pritunl-api.rule=Host(`mypritunlfakeapi.example.com`)" + # - "traefik.http.routers.pritunl-api.service=pritunl-api" + # - "traefik.http.routers.pritunl-api.tls=true" + # - "traefik.http.routers.pritunl-api.tls.certresolver=http" +# networks: +# proxy_external: +# external: true +# name: proxy_external + + ### END SWARM_BLOCK ### + + + + ### END TRAEFIK BLOCK ### + ################################################################# + \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 93da4dd..a761b74 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,6 +11,8 @@ services: build: context: ../server dockerfile: ../docker/Dockerfile + args: + - API_SERVER_DOMAIN=${API_SERVER_DOMAIN:-} restart: always depends_on: - mongodb diff --git a/docs/docker/api-only-install.md b/docs/docker/api-only-install.md new file mode 100644 index 0000000..88fc927 --- /dev/null +++ b/docs/docker/api-only-install.md @@ -0,0 +1,36 @@ +# API Only: Docker + +## Only installs the API (webserver) and not the Pritunl VPN itself. +This approach runs this API, either on port 80 or behind Traefik, either on docker swarm or single daemon. + +You need to have docker up and running on your server. + +- In your server, clone this repo, then `cd` to the cloned folder. +- Copy the docker-compose file provided in `/docker/api-only/docker-compose.yml` to + the root of the cloned folder. + + You shall now have: `/docker-compose.yml` +- Modify the `/docker-compose.yml` to fit your needs and config + + _Watch for volumes, docker swarm or single daemon, behind Traefik or not and the HOST value if behind traefik:_ + + In case you run behind Traefik, you need to setup the traefik router HOST + + You need correctly setup traefik and docker network (here called proxy_external) + + **(!) Make sure the mount volumes match correctly.** + + * The first volume is the path to the www folder from the root of this repo. + + The path shall be a full path, or be next to this docker-compose.yml file. + + No parent folder navigation like `../../../` is allowed by docker. + + * The second volume is the path to the nginx server config file. + + This needs the commited nginx server config (or your own adapted version) to work properly. + + See the file `/docker/api-only/conf.d/pritunl-fake-api.conf` for more details. +- Run the updated `docker-compose.yml` file in daemon mode with: + + `docker-compose up -d` \ No newline at end of file diff --git a/docs/docker/pritunl-patched-install.md b/docs/docker/pritunl-patched-install.md new file mode 100644 index 0000000..2556f1a --- /dev/null +++ b/docs/docker/pritunl-patched-install.md @@ -0,0 +1,28 @@ +# Fully Patched Pritunl: Using Docker + +## Only installs the API (webserver) and not the Pritunl VPN itself. +You need to have docker up and running on your server. + +This uses the docker image for Pritunl by `goofball222/pritunl` and installs the fake api hooks directly into it. + + +Step: + +- In your server, clone this repo, then `cd` to the cloned folder. +- Go to the `docker` folder of the repo. +- Read the `/docker/docker-compose.yml` file carefully and edit to fit your needs (ports, volumes, network, server domain...) +- Run the `docker-compose.yml` file in daemon mode with: + + `docker-compose up -d` + + - This will `docker build` the patched pritunl container and run it on the following ports: + - Under this port the Pritunl web interface will be exposed (for reverse proxies) + + *9700:9700* + + - The following are the two default ports for the tcp+udp servers (you may edit these as needed!) + + *1194:1194* + + *1194:1194/udp* + diff --git a/docs/nginx/hard_nginx.conf b/docs/nginx/hard_nginx.conf new file mode 100644 index 0000000..24a49d0 --- /dev/null +++ b/docs/nginx/hard_nginx.conf @@ -0,0 +1,44 @@ +worker_processes auto; +error_log stderr warn; +pid /run/nginx.pid; + +events { + worker_connections 64; +} + +http { + include mime.types; + default_type application/octet-stream; + + # Define custom log format to include reponse times + log_format main_timed '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '$request_time $upstream_response_time $pipe $upstream_cache_status'; + + access_log /dev/stdout main_timed; + error_log /dev/stderr notice; + + keepalive_timeout 65; + + # Write temporary files to /tmp so they can be created as a non-privileged user + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + # Hardening + proxy_hide_header X-Powered-By; + fastcgi_hide_header X-Powered-By; + server_tokens off; + + gzip on; + gzip_proxied any; + gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss; + gzip_vary on; + gzip_disable "msie6"; + + # Include other server configs + include /etc/nginx/conf.d/*.conf; +} diff --git a/docs/nginx/install.md b/docs/nginx/install.md new file mode 100644 index 0000000..f481a52 --- /dev/null +++ b/docs/nginx/install.md @@ -0,0 +1,105 @@ +# HowTo install the selfhost api variant on Nginx + +## Easy way: +Use docker and docker-compose files provided in `docker/api-only` folder. + +See documentation [Docker Install](docs/docker/api-only-install.md). + +The docker compose file has a detailed help in its top too. Read and Roll :) + + +## Hard way: +First, you need to install Nginx. +```bash +sudo apt-get install nginx +``` + +After that, install all of the relevant PHP modules: + +```bash +sudo apt-get -y install php7.4-fpm php7.4-mysql php7.4-curl php7.4-gd php7.4-intl php-pear php-imagick php7.4-imap php-memcache +``` + +Then install certbot for free SSL certs: +```bash +sudo apt-get install -y certbot python3-certbot-nginx +``` + +After this, create a basic site config for the fake api server. Do this by creating a file under /etc/nginx/sites-available/ and create a symbolic link to /etc/nginx/sites-enabled. +You can refer to the provided Nginx server block available in: +`/docker/api-only/conf.d/pritunl-fake-api.conf` + +Then generate an SSL certificate for the website with certbot. +```bash +sudo certbot --nginx -d [PUBLIC_ACCESSIBLE_API_DOMAIN] +``` + +Once this is done, you should check if you have all the required loaded PHP modules for this server. You can check this by running php -m, and the output should list your PHP modules. + +the output should look be something like: +```bash +#... + core_module (static) + so_module (static) + watchdog_module (static) + http_module (static) + log_config_module (static) + logio_module (static) + version_module (static) + unixd_module (static) + access_compat_module (shared) + alias_module (shared) + auth_basic_module (shared) + authn_core_module (shared) + authn_file_module (shared) + authz_core_module (shared) + authz_host_module (shared) + authz_user_module (shared) + autoindex_module (shared) + deflate_module (shared) + dir_module (shared) + env_module (shared) + filter_module (shared) + http2_module (shared) + mime_module (shared) + mpm_prefork_module (shared) + negotiation_module (shared) + php7_module (shared) + proxy_module (shared) + proxy_fcgi_module (shared) + reqtimeout_module (shared) + rewrite_module (shared) + setenvif_module (shared) + socache_shmcb_module (shared) + ssl_module (shared) + status_module (shared) + #... +``` + +Then clone this repository if you haven't done this already and cd into the root of the project: +```bash +git clone https://gitlab.simonmicro.de/simonmicro/pritunl-fake-api.git +cd ./pritunl-fake-api +``` + +After this is done, copy over the API server files to the server and set permissions. +```bash +sudo cp -R ./www/* /var/www/html/ +sudo chown www-data:www-data -R /var/www/html +sudo chmod -R 774 /var/www/html/ +``` + +For your convenience, a hardened Nginx configuration is provided to help you secure and improve your server, +Read it carefully before use and make sure you understand what it does. + +See: `/docs/nginx/hard_nginx.conf` + + +Then restart Nginx to make sure all of the configuration is loaded. +```bash +sudo systemctl restart nginx +``` + +Once this is done, you should get a response when you visit + + `https://[PUBLIC_ACCESSIBLE_API_DOMAIN]/notification`! diff --git a/server/setup.py b/server/setup.py index 9a66ac7..ed5c648 100644 --- a/server/setup.py +++ b/server/setup.py @@ -7,7 +7,7 @@ import argparse originalApiServer = 'app.pritunl.com' originalAuthServer = 'auth.pritunl.com' -newApiServer = 'pritunl-api.simonmicro.de' +defaultApiServer = 'pritunl-api.simonmicro.de' searchIn = [*glob.glob('/usr/lib/python3*'), '/usr/lib/pritunl/', '/usr/share/pritunl/www/', '/usr/lib/pritunl/', '/usr/share/pritunl/www/'] print(" ____ _ _ _ _____ _ _ ____ ___ ") @@ -22,8 +22,10 @@ interactive = True parser = argparse.ArgumentParser() parser.add_argument('--install', type=str, default='DEFAULT', nargs='?', help='Do not ask and install new API endpoint.') parser.add_argument('--reset', type=str, default='DEFAULT', nargs='?', help='Do not ask and remove new API endpoint.') +parser.add_argument('--api-server', type=str, default=defaultApiServer, help='Set new API server.') args = parser.parse_args() +newApiServer = args.api_server if args.api_server.strip() != '' else defaultApiServer if args.install != 'DEFAULT': interactive = False newApiServer = args.install if args.install is not None else newApiServer