Allow optional decoupling of authoritative nameservice from reverse proxy
As it stands now, the float-infra-dns
role is deployed on the frontend
nodes. This works fine, but there are cases where decoupling things makes sense. For example, in our case someone decided to DDoS one website, which is behind the reverse proxy, using a DNSSEC UDP amplification attack. The attacker was 'mad' at the website, and because of the tight coupling, we could not filter out this UDP traffic and instead they managed to exploit the fact that the DNS server (which requires UDP) is on the same IP as the reverse proxy (which does not necessarily need UDP) resulting in a successful attack.
We were able to separate the authoritative DNS service to its own IP, and then filter the garbage UDP traffic on the reverse proxy and keep the website alive. The attacker would have to turn their attack against the DNS server to continue to impact us on a UDP level, something they are not sophisticated enough to understand.
In order to do that, we did the following (along with setting the nameservers_service
variable):
diff --git a/float/playbooks/frontend.yml b/float/playbooks/frontend.yml
index 6c59341..27fe8e8 100644
--- a/float/playbooks/frontend.yml
+++ b/float/playbooks/frontend.yml
@@ -6,9 +6,13 @@
gather_facts: no
roles:
- float-infra-nginx
- - float-infra-dns
- float-infra-haproxy
+- hosts: dns
+ gather_facts: no
+ roles:
+ - float-infra-dns
+
- hosts: admin_dashboard
gather_facts: no
roles:
diff --git a/float/roles/float-infra-dns/defaults/main.yml b/float/roles/float-infra-dns/defaults/main.yml
index 34f1562..b155504 100644
--- a/float/roles/float-infra-dns/defaults/main.yml
+++ b/float/roles/float-infra-dns/defaults/main.yml
@@ -3,3 +3,6 @@
# The domain name used for NS and MX records for autogenerated zones.
# By default, this is the first public domain.
mx_ns_domain: "{{ domain_public[0] }}"
+
+# Float service where the authoritative nameservers are running.
+nameservers_service: "frontend"
diff --git a/float/roles/float-infra-dns/templates/dns/infra.yml b/float/roles/float-infra-dns/templates/dns/infra.yml
index fa0cd60..dfbacef 100644
--- a/float/roles/float-infra-dns/templates/dns/infra.yml
+++ b/float/roles/float-infra-dns/templates/dns/infra.yml
@@ -2,7 +2,7 @@
"@ns":
_:
-{% for h in services['frontend'].hosts|sort %}
+{% for h in services[nameservers_service].hosts|sort %}
- NS ns{{ loop.index }}.{{ mx_ns_domain }}.
{% if 'ip6' in hostvars[h] %}
- NS ns{{ loop.index }}-v6.{{ mx_ns_domain }}.
@@ -27,10 +27,10 @@
www: CNAME www.l.{{ d }}.
# The explicit NS delegation for 'l' is necessary for dnssec-sign to work properly.
l:
-{% for h in services['frontend'].hosts|sort %}
+{% for h in services[nameservers_service].hosts|sort %}
- NS ns{{ loop.index }}.{{ mx_ns_domain }}.
{% endfor %}
-{% for h in services['frontend'].hosts|sort %}
+{% for h in services[nameservers_service].hosts|sort %}
ns{{ loop.index }}: {{ hostvars[h]['public_ip'] | default(hostvars[h]['ip']) }}
{% if 'ip6' in hostvars[h] or 'public_ip6' in hostvars[h] %}
ns{{ loop.index }}-v6: AAAA {{ hostvars[h]['public_ip6'] | default(hostvars[h]['ip6']) }}
diff --git a/float/roles/float-infra-prometheus/templates/prometheus.yml.j2 b/float/roles/float-infra-prometheus/templates/prometheus.yml.j2
index 2719acb..57cc9b0 100644
--- a/float/roles/float-infra-prometheus/templates/prometheus.yml.j2
+++ b/float/roles/float-infra-prometheus/templates/prometheus.yml.j2
@@ -181,7 +181,7 @@ scrape_configs:
replacement: {{ prober_host }}
static_configs:
- targets:
-{% for host in services['frontend'].hosts|sort %}
+{% for host in services[nameservers_service | default('frontend')].hosts|sort %}
- "{{ host }}"
{% endfor %}
labels:
diff --git a/float/services.yml.default b/float/services.yml.default
index 38913e8..9148708 100644
--- a/float/services.yml.default
+++ b/float/services.yml.default
@@ -11,19 +11,23 @@ frontend:
systemd_services:
- nginx.service
- sso-proxy.service
- - bind9.service
- replds@acme.service
ports:
- 5005
- monitoring_endpoints:
- - name: bind
- port: 9119
- scheme: http
volumes:
- name: cache
path: /var/cache/nginx
size: 20g
+dns:
+ scheduling_group: dns_servers
+ systemd_services:
+ - bind9.service
+ monitoring_endpoints:
+ - name: bind
+ port: 9119
+ scheme: http
+
reports-collector:
scheduling_group: frontend
containers:
diff --git a/float/services.yml.no-elasticsearch b/float/services.yml.no-elasticsearch
index 797abd9..b3a703b 100644
--- a/float/services.yml.no-elasticsearch
+++ b/float/services.yml.no-elasticsearch
@@ -11,19 +11,23 @@ frontend:
systemd_services:
- nginx.service
- sso-proxy.service
- - bind9.service
- replds@acme.service
ports:
- 5005
- monitoring_endpoints:
- - name: bind
- port: 9119
- scheme: http
volumes:
- name: cache
path: /var/cache/nginx
size: 20g
+dns:
+ scheduling_group: dns_servers
+ systemd_services:
+ - bind9.service
+ monitoring_endpoints:
+ - name: bind
+ port: 9119
+ scheme: http
+
log-collector:
scheduling_group: backend
num_instances: 1
but this is not something that float upstream wants because not everyone will want to have separated dns servers.
One solution would be for float to always have a separate dns service, in services.yml.default
, with scheduling_group: frontend
and then we would just need to override its scheduling_group in our own services.yml. This would allow for getting rid of the nameservers_service
variable as it will always just be "dns".