2638: further finishing touches for restful api r=mergify[bot] a=Diman0

- Fix setup utility setting correct value to env var API. It now also sets `false` when the API is disabled in the setup utility.
- Fix IF statement for enabling API in nginx.conf. Setting a different value than `API=true` in mailu.env now disabled the API endpoint in nginx.
- Use safer command for regenerating example API token. It uses crypto.getRandomValues() (as suggested by nextgens) which should be more random than the previously used method. 

## What type of PR?

bug-fix

## What does this PR do?

### Related issue(s)

## Prerequisites
Before we can consider review and merge, please make sure the following list is done and checked.
If an entry in not applicable, you can check it or remove it from the list.

- [ ] In case of feature or enhancement: documentation updated accordingly
- [ ] Unless it's docs or a minor change: add [changelog](https://mailu.io/master/contributors/workflow.html#changelog) entry file.


Co-authored-by: Dimitri Huisman <diman@huisman.xyz>
main
bors[bot] 1 year ago committed by GitHub
commit 4a24bd9e24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,8 +5,7 @@ import logging as log
import sys
from socrate import system, conf
system.set_env()
args = os.environ.copy()
args = system.set_env()
log.basicConfig(stream=sys.stderr, level=args.get("LOG_LEVEL", "WARNING"))
args['TLS_PERMISSIVE'] = str(args.get('TLS_PERMISSIVE')).lower() not in ('false', 'no')
@ -18,8 +17,8 @@ with open("/etc/resolv.conf") as handle:
args["RESOLVER"] = f"[{resolver}]" if ":" in resolver else resolver
# TLS configuration
cert_name = os.getenv("TLS_CERT_FILENAME", default="cert.pem")
keypair_name = os.getenv("TLS_KEYPAIR_FILENAME", default="key.pem")
cert_name = args.get("TLS_CERT_FILENAME", "cert.pem")
keypair_name = args.get("TLS_KEYPAIR_FILENAME", "key.pem")
args["TLS"] = {
"cert": ("/certs/%s" % cert_name, "/certs/%s" % keypair_name),
"letsencrypt": ("/certs/letsencrypt/live/mailu/nginx-chain.pem",
@ -37,7 +36,7 @@ def format_for_nginx(fullchain, output):
split = '-----END CERTIFICATE-----\n'
with open(fullchain, 'r') as pem:
certs = [f'{cert}{split}' for cert in pem.read().split(split) if cert]
if len(certs)>2 and os.getenv('LETSENCRYPT_SHORTCHAIN'):
if len(certs)>2 and args.get('LETSENCRYPT_SHORTCHAIN'):
del certs[-1]
with open(output, 'w') as pem:
pem.write(''.join(certs))

@ -1,17 +1,17 @@
Using an external reverse proxy
===============================
One of Mailu's use cases is as part of a larger services platform, where maybe
One of Mailu's use cases is as part of a larger services platform, where maybe
other Web services are available than just Mailu Webmail and Admin interfaces.
In such a configuration, one would usually run a frontend reverse proxy to serve all
Web contents based on criteria like the requested hostname (virtual hosts)
and/or the requested path.
In such a configuration, one would usually run a frontend reverse proxy to serve all
Web contents based on criteria like the requested hostname (virtual hosts)
and/or the requested path.
The Mailu Admin Web frontend is disabled in the default setup for security reasons,
it is however expected that most users will enable it at some point. Also, due
to the Docker Compose configuration structure, it is impossible for us to facilitate
disabling the Web frontend with a configuration variable. This guide was written to
The Mailu Admin Web frontend is disabled in the default setup for security reasons,
it is however expected that most users will enable it at some point. Also, due
to the Docker Compose configuration structure, it is impossible for us to facilitate
disabling the Web frontend with a configuration variable. This guide was written to
help users setup such an architecture.
There are basically three options, from the most to the least recommended one:
@ -22,13 +22,13 @@ There are basically three options, from the most to the least recommended one:
All options will require that you modify the ``docker-compose.yml`` and ``mailu.env`` file.
Mailu must also be configured with the information what header is used by the reverse proxy for passing the remote client IP.
Mailu must also be configured with the information what header is used by the reverse proxy for passing the remote client IP.
This is configured in the mailu.env file. See the :ref:`configuration reference <reverse_proxy_headers>` for more information.
Have Mailu Web frontend listen locally
--------------------------------------
The simplest and safest option is to modify the port forwards for Mailu Web frontend and have your own frontend point there.
The simplest and safest option is to modify the port forwards for Mailu Web frontend and have your own frontend point there.
For instance, in the ``front`` section of Mailu ``docker-compose.yml``, use local ports 8080 and 8443 respectively for HTTP and HTTPS:
.. code-block:: yaml
@ -45,7 +45,7 @@ For instance, in the ``front`` section of Mailu ``docker-compose.yml``, use loca
volumes:
- "$ROOT/certs:/certs"
Then on your own frontend, point to these local ports. In practice, you only need to point to the HTTPS port
Then on your own frontend, point to these local ports. In practice, you only need to point to the HTTPS port
(as the HTTP port simply redirects there). Here is an example Nginx configuration:
.. code-block:: nginx
@ -68,19 +68,19 @@ Then on your own frontend, point to these local ports. In practice, you only nee
#mailu.env file
REAL_IP_HEADER=X-Real-IP
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
Because the admin interface is served as ``/admin``, the Webmail as ``/webmail``, the single sign on page as ``/sso``, webdav as ``/webdav``, the client-autoconfiguration and the static files endpoint as ``/static``, you may also want to use a single virtual host and serve other applications (still Nginx):
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
Because the admin interface is served as ``/admin``, the RESTful API as ``/api``, the Webmail as ``/webmail``, the single sign on page as ``/sso``, webdav as ``/webdav``, the client-autoconfiguration and the static files endpoint as ``/static``, you may also want to use a single virtual host and serve other applications (still Nginx):
.. code-block:: nginx
server {
# [...] here goes your standard configuration
location ~* ^/(admin|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover) {
location ~* ^/(admin|api|sso|static|webdav|webmail|(apple\.)?mobileconfig|(\.well\-known/autoconfig/)?mail/|Autodiscover/Autodiscover) {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass https://localhost:8443;
proxy_pass https://localhost:8443;
}
location /main_app {
@ -103,13 +103,13 @@ Because the admin interface is served as ``/admin``, the Webmail as ``/webmail``
.. note:: Please dont add a ``/`` at the end of the location pattern or all your redirects will fail with 404 because the ``/`` would be missing, and you would have to add it manually to move on
.. code-block:: docker
#mailu.env file
REAL_IP_HEADER=X-Real-IP
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
Finally, you might want to serve the admin interface on a separate virtual host but not expose the admin container
Finally, you might want to serve the admin interface on a separate virtual host but not expose the admin container
directly (have your own HTTPS virtual hosts on top of Mailu, one public for the Webmail and one internal for administration for instance).
Here is an example configuration :
@ -147,7 +147,7 @@ Here is an example configuration :
#mailu.env file
REAL_IP_HEADER=X-Real-IP
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
Depending on how you access the front server, you might want to add a ``proxy_redirect`` directive to your ``location`` blocks:
@ -166,8 +166,8 @@ Traefik as reverse proxy
As such, many may wish to integrate Mailu into a system which already uses Traefik as its sole ingress/reverse-proxy.
As the ``mailu/front`` container uses Nginx not only for ``HTTP`` forwarding, but also for the mail-protocols like ``SMTP``, ``IMAP``, etc
, we need to keep this container around even when using another ``HTTP`` reverse-proxy. Furthermore, Traefik is neither able to
forward non-HTTP, nor can it easily forward HTTPS-to-HTTPS.
, we need to keep this container around even when using another ``HTTP`` reverse-proxy. Furthermore, Traefik is neither able to
forward non-HTTP, nor can it easily forward HTTPS-to-HTTPS.
This, however, means 3 things:
@ -175,9 +175,9 @@ This, however, means 3 things:
- ``mailu/front`` is not exposed to the outside world on ``HTTP``
- ``mailu/front`` still needs ``SSL`` certificates (here, we assume ``letsencrypt``) for a well-behaved mail service
This makes the setup with Traefik a bit harder: Traefik saves its certificates in a proprietary *JSON* file, which is not readable
by Nginx in the ``front``-container. To solve this, your ``acme.json`` needs to be exposed to the host or a ``docker-volume``.
It will then be read by a script in another container, which will dump the certificates as ``PEM`` files, readable for
This makes the setup with Traefik a bit harder: Traefik saves its certificates in a proprietary *JSON* file, which is not readable
by Nginx in the ``front``-container. To solve this, your ``acme.json`` needs to be exposed to the host or a ``docker-volume``.
It will then be read by a script in another container, which will dump the certificates as ``PEM`` files, readable for
Nginx. The ``front`` container will automatically reload Nginx whenever these certificates change.
To set this up, first set ``TLS_FLAVOR=mail`` in your ``.env``. This tells ``mailu/front`` not to try to request certificates using ``letsencrypt``,
@ -194,20 +194,20 @@ Add the respective Traefik labels for your domain/configuration, like
.. note:: Please dont forget to add ``TRAEFIK_DOMAIN=[...]`` TO YOUR ``.env``
If your Traefik is configured to automatically request certificates from *letsencrypt*, then youll have a certificate
for ``mail.your.example.com`` now. However, ``mail.your.example.com`` might only be the location where you want the Mailu web-interfaces
If your Traefik is configured to automatically request certificates from *letsencrypt*, then youll have a certificate
for ``mail.your.example.com`` now. However, ``mail.your.example.com`` might only be the location where you want the Mailu web-interfaces
to live — your mail should be sent/received from ``your.example.com``, and this is the ``DOMAIN`` in your ``.env``?
To support that use-case, Traefik can request ``SANs`` for your domain. The configuration for this will depend on your Traefik version.
Mailu must also be configured with the information what header is used by the reverse proxy for passing the remote
Mailu must also be configured with the information what header is used by the reverse proxy for passing the remote
client IP. This is configured in mailu.env:
.. code-block:: docker
#mailu.env file
REAL_IP_HEADER=X-Real-Ip
REAL_IP_FROM=x.x.x.x,y.y.y.y.y
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
#x.x.x.x,y.y.y.y.y is the static IP address your reverse proxy uses for connecting to Mailu.
For more information see the :ref:`configuration reference <reverse_proxy_headers>` for more information.
@ -235,7 +235,7 @@ Add the appropriate labels for your domain(s) to the ``front`` container in ``do
Of course, be sure to define the Certificate Resolver ``foo`` in the static configuration as well.
Alternatively, you can define SANs in the Traefik static configuration using routers, or in the static configuration using entrypoints.
Alternatively, you can define SANs in the Traefik static configuration using routers, or in the static configuration using entrypoints.
Refer to the Traefik documentation for more details.
.. _`Traefik`: https://traefik.io/

@ -1,12 +1,5 @@
//API_TOKEN generator
var chars = "0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var tokenLength = 12;
var token = "";
for (var i = 0; i <= tokenLength; i++) {
var randomNumber = Math.floor(Math.random() * chars.length);
token += chars.substring(randomNumber, randomNumber +1);
}
//Store API token in variable.
var token = $("#api_token").val();
$(document).ready(function() {
if ($("#webmail").val() == 'none') {
@ -44,7 +37,7 @@ $(document).ready(function() {
});
$(document).ready(function() {
if ($('#api').prop('checked')) {
if ($('#api_enabled').prop('checked')) {
$("#api_path").show();
$("#api_path").val("/api")
$("#api_token").show();
@ -53,13 +46,13 @@ $(document).ready(function() {
$("#api_token_label").show();
} else {
$("#api_path").hide();
$("#api_path").val("/api")
$("#api_path").val("")
$("#api_token").hide();
$("#api_token").prop('required',false);
$("#api_token").val("");
$("#api_token_label").hide();
}
$("#api").change(function() {
$("#api_enabled").change(function() {
if ($(this).is(":checked")) {
$("#api_path").show();
$("#api_path").val("/api");
@ -69,7 +62,7 @@ $(document).ready(function() {
$("#api_token_label").show();
} else {
$("#api_path").hide();
$("#api_path").val("/api")
$("#api_path").val("")
$("#api_token").hide();
$("#api_token").prop('required',false);
$("#api_token").val("");

@ -93,11 +93,11 @@ manage your email domains, users, etc.</p>
It is not possible to use the API without an API token.</p>
<div class="form-group">
<input type="checkbox" name="api_enabled" value="false" id="api" >
<input type="checkbox" name="api_enabled" value="true" id="api_enabled" >
<label>Enable the API (and path to the API)</label>
<input class="form-control" type="text" name="api_path" id="api_path" style="display: none">
<label name="api_token_label" id="api_token_label">API token</label>
<input class="form-control" type="text" name="api_token" id="api_token" style="display: none">
<input class="form-control" type="text" name="api_token" id="api_token" style="display: none" value="{{ secret(32) }}">
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

Loading…
Cancel
Save