Merge pull request #681 from usrpro/feat-unbound-dns

Unbound DNS as optional service
This commit is contained in:
mergify[bot]
2018-10-30 17:32:13 +00:00
committed by GitHub
10 changed files with 144 additions and 1 deletions

View File

@@ -0,0 +1,18 @@
FROM python:3-alpine
RUN apk add --no-cache unbound curl bind-tools \
&& pip3 install jinja2 \
&& curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \
&& chown root:unbound /etc/unbound \
&& chmod 775 /etc/unbound \
&& apk del --no-cache curl \
&& /usr/sbin/unbound-anchor -a /etc/unbound/trusted-key.key | true
COPY start.py /start.py
COPY unbound.conf /unbound.conf
EXPOSE 53/udp 53/tcp
CMD /start.py
HEALTHCHECK CMD dig @127.0.0.1 || exit 1

9
services/unbound/start.py Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/local/bin/python3
import jinja2
import os
convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ))
convert("/unbound.conf", "/etc/unbound/unbound.conf")
os.execv("/usr/sbin/unbound", ["-c /etc/unbound/unbound.conf"])

View File

@@ -0,0 +1,19 @@
server:
verbosity: 1
interface: 0.0.0.0
interface: ::0
logfile: /dev/stdout
do-ip4: yes
do-ip6: yes
do-udp: yes
do-tcp: yes
do-daemonize: no
access-control: {{ SUBNET }} allow
directory: "/etc/unbound"
username: root
auto-trust-anchor-file: trusted-key.key
root-hints: "/etc/unbound/root.hints"
hide-identity: yes
hide-version: yes
max-udp-size: 4096
msg-buffer-size: 65552

View File

@@ -28,6 +28,16 @@ services:
{% endfor %} {% endfor %}
volumes: volumes:
- "{{ root }}/certs:/certs" - "{{ root }}/certs:/certs"
{% if resolver_enabled %}
resolver:
image: mailu/unbound:{{ version }}
env_file: {{ env }}
restart: always
networks:
default:
ipv4_address: {{ dns }}
{% endif %}
admin: admin:
image: mailu/admin:{{ version }} image: mailu/admin:{{ version }}
@@ -58,6 +68,11 @@ services:
- "{{ root }}/overrides:/overrides" - "{{ root }}/overrides:/overrides"
depends_on: depends_on:
- front - front
{% if resolver_enabled %}
- resolver
dns:
- {{ dns }}
{% endif %}
# Optional services # Optional services
{% if antispam_enabled %} {% if antispam_enabled %}
@@ -70,6 +85,11 @@ services:
- "{{ root }}/overrides/rspamd:/etc/rspamd/override.d" - "{{ root }}/overrides/rspamd:/etc/rspamd/override.d"
depends_on: depends_on:
- front - front
{% if resolver_enabled %}
- resolver
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if antivirus_enabled %} {% if antivirus_enabled %}
@@ -78,6 +98,12 @@ services:
env_file: {{ env }} env_file: {{ env }}
volumes: volumes:
- "{{ root }}/filter:/data" - "{{ root }}/filter:/data"
{% if resolver_enabled %}
depends_on:
- resolver
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if webdav_enabled %} {% if webdav_enabled %}
@@ -92,6 +118,12 @@ services:
fetchmail: fetchmail:
image: mailu/fetchmail:{{ version }} image: mailu/fetchmail:{{ version }}
env_file: {{ env }} env_file: {{ env }}
{% if resolver_enabled %}
depends_on:
- resolver
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
# Webmail # Webmail
@@ -104,3 +136,13 @@ services:
depends_on: depends_on:
- imap - imap
{% endif %} {% endif %}
{% if resolver_enabled %}
networks:
default:
driver: bridge
ipam:
driver: default
config:
- subnet: {{ subnet }}
{% endif %}

View File

@@ -25,6 +25,9 @@ SECRET_KEY={{ secret(16) }}
# PUBLIC_IPV4= {{ bind4 }} (default: 127.0.0.1) # PUBLIC_IPV4= {{ bind4 }} (default: 127.0.0.1)
# PUBLIC_IPV6= {{ bind6 }} (default: ::1) # PUBLIC_IPV6= {{ bind6 }} (default: ::1)
# Subnet
SUBNET={{ subnet }}
# Main mail domain # Main mail domain
DOMAIN={{ domain }} DOMAIN={{ domain }}

View File

@@ -28,6 +28,15 @@ services:
- "{{ root }}/certs:/certs" - "{{ root }}/certs:/certs"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
resolver:
image: mailu/unbound:{{ version }}
env_file: {{ env }}
networks:
default:
ipv4_address: {{ dns }}
{% endif %}
admin: admin:
image: mailu/admin:{{ version }} image: mailu/admin:{{ version }}
@@ -63,6 +72,10 @@ services:
- "{{ root }}/overrides:/overrides" - "{{ root }}/overrides:/overrides"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
# Optional services # Optional services
{% if antispam_enabled %} {% if antispam_enabled %}
@@ -77,6 +90,10 @@ services:
- "{{ root }}/overrides/rspamd:/etc/rspamd/override.d" - "{{ root }}/overrides/rspamd:/etc/rspamd/override.d"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if antivirus_enabled %} {% if antivirus_enabled %}
@@ -87,6 +104,10 @@ services:
- "{{ root }}/filter:/data" - "{{ root }}/filter:/data"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if webdav_enabled %} {% if webdav_enabled %}
@@ -107,6 +128,10 @@ services:
- "{{ root }}/data:/data" - "{{ root }}/data:/data"
deploy: deploy:
replicas: 1 replicas: 1
{% if resolver_enabled %}
dns:
- {{ dns }}
{% endif %}
{% endif %} {% endif %}
{% if webmail_type != 'none' %} {% if webmail_type != 'none' %}

View File

@@ -7,6 +7,7 @@ import jinja2
import uuid import uuid
import string import string
import random import random
import ipaddress
app = flask.Flask(__name__) app = flask.Flask(__name__)
@@ -75,6 +76,7 @@ def build_app(path):
def submit(): def submit():
data = flask.request.form.copy() data = flask.request.form.copy()
data['uid'] = str(uuid.uuid4()) data['uid'] = str(uuid.uuid4())
data['dns'] = str(ipaddress.IPv4Network(data['subnet'])[-2])
db.set(data['uid'], json.dumps(data)) db.set(data['uid'], json.dumps(data))
return flask.redirect(flask.url_for('.setup', uid=data['uid'])) return flask.redirect(flask.url_for('.setup', uid=data['uid']))

View File

@@ -26,6 +26,19 @@ avoid generic all-interfaces addresses like <code>0.0.0.0</code> or <code>::</co
pattern="^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"> pattern="^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$">
</div> </div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="resolver_enabled" value="true">
Enable unbound resolver
</label>
</div>
<div class="form-group">
<label>Subnet</label>
<input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"
value="192.168.0.0/24">
</div>
<p>You server will be available under a main hostname but may expose multiple public <p>You server will be available under a main hostname but may expose multiple public
hostnames. Every e-mail domain that points to this server must have one of the hostnames. Every e-mail domain that points to this server must have one of the
hostnames in its <code>MX</code> record. Hostnames must be coma-separated.</p> hostnames in its <code>MX</code> record. Hostnames must be coma-separated.</p>

View File

@@ -3,9 +3,17 @@
and let users access their mailboxes. Mailu has some flexibility in the way and let users access their mailboxes. Mailu has some flexibility in the way
you expose it to the world.</p> you expose it to the world.</p>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="resolver_enabled" value="true">
Enable unbound resolver
</label>
</div>
<div class="form-group"> <div class="form-group">
<label>Subnet</label> <label>Subnet</label>
<input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"> <input class="form-control" type="text" name="subnet" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))$"
value="192.168.0.0/24">
</div> </div>
<p>You server will be available under a main hostname but may expose multiple public <p>You server will be available under a main hostname but may expose multiple public

View File

@@ -6,6 +6,10 @@ services:
image: ${DOCKER_ORG:-mailu}/nginx:${VERSION:-local} image: ${DOCKER_ORG:-mailu}/nginx:${VERSION:-local}
build: ../core/nginx build: ../core/nginx
resolver:
image: ${DOCKER_ORG:-mailu}/unbound:${VERSION:-local}
build: ../services/unbound
imap: imap:
image: ${DOCKER_ORG:-mailu}/dovecot:${VERSION:-local} image: ${DOCKER_ORG:-mailu}/dovecot:${VERSION:-local}
build: ../core/dovecot build: ../core/dovecot