lilly/cccffr-dns-secondary #162
8 changed files with 264 additions and 61 deletions
update knot role to support more operational variety
Now secondary zones, dynamic zones, interpeted catalog zones, etc. are supported by our knot role
commit
4bdd36b8f7
|
|
@ -1,9 +1,11 @@
|
|||
ansible_pull__age_private_key: ENC[AES256_GCM,data:2kBG8j8JHa/dlXgWMdbSobulFdVunf052T1QQfm1X2vpEZx2HPCL87fWea+O0WOg7+eoMYbiShu0Vw1eTjb+687LjU8l4cj2JWIajnYfDGH+ipWXojxj613C3RZV3JfDOclVTwP8fCHu7z7P3fKrsKWb5d3t2ohTT+sGdVdimakAOf192CkufcVIthq2imiWbntiMTOdMGJxyIjqT2Io2H89nSbJXkONsuHCF/PbxhryB2LZbl8aZV32knk=,iv:hpscVc7iO4r/h31vS6Zno2pkEsgA2uR7wD/1PjH1znM=,tag:ypiwFtgeXuj4gOsgTCRTBw==,type:str]
|
||||
knot__dnssec_key_secret: ENC[AES256_GCM,data:WPFTLyJIttFtqqTZV2fGN0Tt1vRS318TGmd2YqNzYisE3TBi6Z2aClxuYh56Q+j7TUQwCvga3jd5w017sEz3kA==,iv:umaFHBCy9AZgNFv7uXLCtO0o/NZDAZ1QNg5DcGHWEW8=,tag:oR92C1Uj5iXU9L02MqzGSQ==,type:str]
|
||||
knot__keys:
|
||||
- id: ENC[AES256_GCM,data:ff7Z1SHmKVseX4hguwcfrqLKyDKu3Imw1Q==,iv:Ds5T3cvi2XYAa3C8mbgYjN8AHM4FsKR2RTPvykWgy2E=,tag:7Gveno0zsLf3bDOYgkQkbw==,type:str]
|
||||
secret: ENC[AES256_GCM,data:dJcVnqQ6hlA/xp4NQ5s9by+z9cm5oWD7KAlPX+kDsvSJn33YiZ/A/wf0jfT+FeVLaBerQ6WipxZs90rp8rD+dg==,iv:JVby6vORdqChps6cRx1EJPNBC7+M0Vgp1ji1nQtv3K4=,tag:X0KggJahlTOAowTNnpPs2g==,type:str]
|
||||
algorithm: ENC[AES256_GCM,data:r7uojDN+o1yYgaY=,iv:KBWKZbd+5qZstuMh6gseq/vC86UB/l9m4wVZpA4usPk=,tag:gfU3asmwOIgxJQsqPG712g==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age18zgt4y2sd75hxnpe333zz39048ctxpr0q8a3uqh3jajjkyawsdrq8yg5ve
|
||||
enc: |
|
||||
- enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArVUxoOFhZdi9PSkZlU3pw
|
||||
VW0vNVRTSG9EamUvVGR0dnN6ajcwTXdlQkdZCkFGVWxPcTNSOGtJdlpSMXJxOFBX
|
||||
|
|
@ -11,8 +13,9 @@ sops:
|
|||
SHhROG5nMGp2d3dwWVJsZk55TW93dU0KnW2ZhU6OhwwPBIxcO1xP+W8DV5Obj6ov
|
||||
Hgb8MQ6i9FlhAN/P8onBsqvbh0ttBEFQ6aRJj5njKs/MMfKUk9Q25g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-05-01T17:08:09Z"
|
||||
mac: ENC[AES256_GCM,data:TaMWf1ESs8nYzxkElMYtsz+/Be0PtI7FA0q6IFK+ob4dl/EN+AeTD7Pp0MZF8zcRvZ4hF0Ybimet5bwVR+d7UIXlXz3qP//pX68JDCvcLMQuhNtm6Ws+mwVxkpxEvBr1PtxlSvcQ76vH3ryEsXkP84gmlCDEdX1GAZYZ9ZS3Cfk=,iv:g3tzUfTPNUQyOAxWJEFPHg0IAPAzQgwYABHm4mFOOrI=,tag:C6KE/bg/3jS7Wc56y6YOJQ==,type:str]
|
||||
recipient: age18zgt4y2sd75hxnpe333zz39048ctxpr0q8a3uqh3jajjkyawsdrq8yg5ve
|
||||
lastmodified: "2026-09-11T08:40:23Z"
|
||||
mac: ENC[AES256_GCM,data:UrncHXwMWUzs5pciDVUGJ885EBsPfrOFswzDOej2pQOTrp6+YomluHivy/L5niSYb05HaGy+9puw35+d+9SXQbgoI5WbwYo2LQ2MggMWSchnnZoy3O5S6LLjBhcvebDv4/1JGvftyBS1LnU7mmJaMekV5KASXaGyuwKumsdTojA=,iv:HJqCXtqQErP/50tR7LL2eAN574NkVO/3QfYsK1q9tt4=,tag:BniLURhadzSliPYz0cRgkA==,type:str]
|
||||
pgp:
|
||||
- created_at: "2026-05-20T02:08:47Z"
|
||||
enc: |-
|
||||
|
|
@ -185,4 +188,4 @@ sops:
|
|||
-----END PGP MESSAGE-----
|
||||
fp: 41FFAF3D519CF5C039FBD8414BCC213729AF0E49
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.12.1
|
||||
version: 3.13.3
|
||||
|
|
|
|||
|
|
@ -3,18 +3,21 @@ deploy_systemd_resolved_config__enable: false
|
|||
|
||||
alloy_config_additional: "{{ lookup('ansible.builtin.template', 'resources/chaosknoten/auth-dns/alloy/knot-exporter.alloy') }}"
|
||||
|
||||
knot__dnssec_key_id: "auth-dns.hamburg.ccc.de-1"
|
||||
knot__remotes:
|
||||
- id: erfadns.ber.ccc.de
|
||||
address: [ "2a02:8000:1000:101::196", "185.106.84.196" ]
|
||||
via: [ "2a00:14b0:4200:3000:124::1", "212.12.48.124" ]
|
||||
- id: ns.vie.ccc.de
|
||||
address: [ "2a02:1b8:10:31::228", "146.255.57.228" ]
|
||||
via: [ "2a00:14b0:4200:3000:124::1", "212.12.48.124" ]
|
||||
# - id: cccfr-de-hidden-primary
|
||||
# address: [ "89.163.204.252" ]
|
||||
|
||||
knot__catalog_zones:
|
||||
- domain: "hamburg.ccc.de.catalog."
|
||||
notify_targets: [ "erfadns.ber.ccc.de" ]
|
||||
|
||||
knot__zones:
|
||||
knot__primary_zones:
|
||||
- domain: "hh.ccc.de."
|
||||
catalog_member: "hamburg.ccc.de.catalog."
|
||||
notify_targets: [ "erfadns.ber.ccc.de" ]
|
||||
|
|
@ -48,3 +51,7 @@ knot__zones:
|
|||
- domain: "3.2.0.0.0.0.0.f.0.b.4.1.0.0.a.2.ip6.arpa."
|
||||
notify_targets: [ "erfadns.ber.ccc.de" ]
|
||||
content: "{{ lookup('ansible.builtin.file', 'resources/chaosknoten/auth-dns/zones/3.2.0.0.0.0.0.f.0.b.4.1.0.0.a.2.ip6.arpa.zone') }}"
|
||||
|
||||
# knot__catalog_secondary_zones:
|
||||
# - domain: "cccfr.catalog.invalid"
|
||||
# primary: "cccfr-de-hidden-primary"
|
||||
|
|
|
|||
|
|
@ -1,2 +1,11 @@
|
|||
---
|
||||
knot__deploy_prometheus_exporter: true
|
||||
knot__log_level: info
|
||||
knot__remotes: [ ]
|
||||
knot__keys: [ ]
|
||||
knot__catalog_zones: [ ]
|
||||
knot__primary_zones: [ ]
|
||||
knot__catalog_secondary_zones: [ ]
|
||||
knot__secondary_zones: [ ]
|
||||
knot__dynamic_zones: [ ]
|
||||
knot__acls: [ ]
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
argument_specs:
|
||||
main:
|
||||
options:
|
||||
knot__dnssec_key_id:
|
||||
description: The id of the TSIG key which knot will use for zone transfer signing
|
||||
knot__log_level:
|
||||
type: str
|
||||
required: true
|
||||
knot__dnssec_key_secret:
|
||||
description: The secret value of the TSIG key which knot will use for zone transfer signing
|
||||
type: str
|
||||
required: true
|
||||
required: false
|
||||
|
||||
knot__deploy_prometheus_exporter:
|
||||
type: bool
|
||||
required: false
|
||||
description: Whether a prometheus exporter for knot should also be installed
|
||||
|
||||
knot__remotes:
|
||||
description:
|
||||
- A list of definitions for remote nameservers that are used for different purposes
|
||||
|
|
@ -25,11 +26,19 @@ argument_specs:
|
|||
type: list
|
||||
required: true
|
||||
elements: str
|
||||
key:
|
||||
type: str
|
||||
required: false
|
||||
via:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
|
||||
knot__catalog_zones:
|
||||
description: A list of catalog zones that will be served by knot
|
||||
type: list
|
||||
elements: dict
|
||||
required: true
|
||||
required: false
|
||||
options:
|
||||
domain:
|
||||
type: str
|
||||
|
|
@ -38,11 +47,12 @@ argument_specs:
|
|||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
knot__zones:
|
||||
|
||||
knot__primary_zones:
|
||||
description: A list of user zones that will be served by knot
|
||||
type: list
|
||||
elements: dict
|
||||
required: true
|
||||
required: false
|
||||
options:
|
||||
domain:
|
||||
type: str
|
||||
|
|
@ -57,3 +67,105 @@ argument_specs:
|
|||
content:
|
||||
type: str
|
||||
required: true
|
||||
|
||||
knot__dynamic_zones:
|
||||
description: A list of user zones operated via DDNS that will be served by knot
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
options:
|
||||
domain:
|
||||
type: str
|
||||
required: true
|
||||
notify_targets:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
catalog_member:
|
||||
type: str
|
||||
required: false
|
||||
acl:
|
||||
type: list
|
||||
elements: str
|
||||
required: true
|
||||
|
||||
knot__secondary_zones:
|
||||
description: A list of secondary zones that will be served by knot
|
||||
required: false
|
||||
type: list
|
||||
elements: dict
|
||||
options:
|
||||
domain:
|
||||
type: str
|
||||
required: true
|
||||
notify_targets:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
catalog_member:
|
||||
type: str
|
||||
required: false
|
||||
primary:
|
||||
type: str
|
||||
required: true
|
||||
|
||||
knot__catalog_secondary_zones:
|
||||
description: A list of zones which are catalog secondaries and which will generate new zones based on the catalog content
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
options:
|
||||
domain:
|
||||
type: str
|
||||
required: true
|
||||
notify_targets:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
primary:
|
||||
type: str
|
||||
required: true
|
||||
|
||||
knot__keys:
|
||||
description: TSIG Key stanzas used by knot
|
||||
required: false
|
||||
type: list
|
||||
elements: dict
|
||||
options:
|
||||
id:
|
||||
type: str
|
||||
required: true
|
||||
algorithm:
|
||||
type: str
|
||||
required: false
|
||||
secret:
|
||||
type: str
|
||||
required: true
|
||||
|
||||
knot__acls:
|
||||
description: ACL stanzas
|
||||
required: false
|
||||
type: list
|
||||
elements: dict
|
||||
options:
|
||||
id:
|
||||
type: str
|
||||
required: true
|
||||
address:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
key:
|
||||
type: str
|
||||
required: false
|
||||
remote:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
action:
|
||||
type: str
|
||||
required: false
|
||||
update_type:
|
||||
type: list
|
||||
elements: str
|
||||
required: false
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
- name: Deploy configured zones
|
||||
become: true
|
||||
notify: reload knot
|
||||
loop: "{{ knot__zones }}"
|
||||
loop: "{{ knot__primary_zones }}"
|
||||
loop_control:
|
||||
label: "{{ item.domain }}"
|
||||
vars:
|
||||
|
|
@ -34,17 +34,3 @@
|
|||
group: knot
|
||||
mode: u=rw,g=r
|
||||
validate: "kzonecheck -v -o '{{ item.domain }}' %s"
|
||||
|
||||
# this seems weird but hear me out:
|
||||
# if we don't disable SLAAC, the node automatically gets an address based on IPv6 Router-Advertisements
|
||||
# this results in outgoing zone transfers failing because knot will prefer to use the dynamic address over the statically configured one.
|
||||
# so because we are configuring a DNS Nameserver where known IP-Addresses are actually important for ACL reasons, SLAAC is disabled
|
||||
- name: Disable IPv6 SLAAC
|
||||
become: true
|
||||
notify: netplan apply
|
||||
ansible.builtin.template:
|
||||
src: "netplan-disable-ra.yaml"
|
||||
dest: "/etc/netplan/10-disable-ra.yaml"
|
||||
owner: root
|
||||
group: root
|
||||
mode: u=rw,g=,o=
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
---
|
||||
- ansible.builtin.import_tasks: 01-install.yaml # noqa: name[missing]
|
||||
- ansible.builtin.import_tasks: 02-configure.yaml # noqa: name[missing]
|
||||
- ansible.builtin.import_tasks: 03-configure-exporter.yaml # noqa: name[missing]
|
||||
- tags: [ knot ]
|
||||
block:
|
||||
- ansible.builtin.import_tasks: 01-install.yaml # noqa: name[missing]
|
||||
- ansible.builtin.import_tasks: 02-configure.yaml # noqa: name[missing]
|
||||
- ansible.builtin.include_tasks: 03-configure-exporter.yaml # noqa: name[missing]
|
||||
when: knot__deploy_prometheus_exporter
|
||||
|
|
|
|||
|
|
@ -9,15 +9,17 @@ server:
|
|||
|
||||
log:
|
||||
- target: syslog
|
||||
any: info
|
||||
any: {{ knot__log_level }}
|
||||
|
||||
database:
|
||||
storage: "/var/lib/knot"
|
||||
|
||||
key:
|
||||
- id: {{ knot__dnssec_key_id }}
|
||||
algorithm: hmac-sha512
|
||||
secret: "{{ knot__dnssec_key_secret }}"
|
||||
{% for i_key in knot__keys -%}
|
||||
- id: "{{ i_key.id }}"
|
||||
algorithm: "{{ i_key.algorithm | default("hmac-sha256") }}"
|
||||
secret: "{{ i_key.secret }}"
|
||||
{% endfor %}
|
||||
|
||||
remote:
|
||||
# static, external and public remote used for DNSSEC KSK checking
|
||||
|
|
@ -28,6 +30,13 @@ remote:
|
|||
{% for i_remote in knot__remotes -%}
|
||||
- id: "{{ i_remote.id }}"
|
||||
address: [ {% for i_addr in i_remote.address %}"{{ i_addr}}"{% if not loop.last %},{% endif %} {% endfor %} ]
|
||||
{% if i_remote.via | default(None) -%}
|
||||
via: [ {% for i_via in i_remote.via %}"{{ i_via }}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
{% endif -%}
|
||||
{% if i_remote.key | default(None) -%}
|
||||
key: "{{ i_remote.key }}"
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
|
@ -46,10 +55,33 @@ policy:
|
|||
nsec3: true
|
||||
nsec3-salt-length: 0
|
||||
|
||||
# explicit acl configurations
|
||||
# we mostly rely on automatic ACLs but for some dynamic zones, explicit allow stanzas are required
|
||||
acl:
|
||||
{% for i_acl in knot__acls -%}
|
||||
- id: "{{ i_acl.id }}"
|
||||
{% if i_acl.address | default(None) -%}
|
||||
address: [ {% for i_addr in i_acl.address %}"{{ i_addr}}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
{% endif -%}
|
||||
{% if i_acl.key | default(None) -%}
|
||||
key: "{{ i_acl.key }}"
|
||||
{% endif -%}
|
||||
{% if i_acl.remote | default(None) -%}
|
||||
remote: "{{ i_acl.remote }}"
|
||||
{% endif -%}
|
||||
{% if i_acl.action | default(None) -%}
|
||||
action: "{{ i_acl.action }}"
|
||||
{% endif -%}
|
||||
{% if i_acl.update_type | default(None) -%}
|
||||
update-type: [ {% for i_addr in i_acl.update_type %}"{{ i_addr}}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# define default settings that apply to all zones
|
||||
template:
|
||||
# template for general-purpose user zones
|
||||
- id: default
|
||||
# template for general-purpose primary zones
|
||||
- id: primary
|
||||
storage: "/etc/knot/zones"
|
||||
file: "%s.zone"
|
||||
semantic-checks: on
|
||||
|
|
@ -57,17 +89,45 @@ template:
|
|||
zonefile-load: difference-no-serial
|
||||
serial-policy: dateserial
|
||||
journal-content: all
|
||||
default-ttl: 7200
|
||||
dnssec-signing: on
|
||||
dnssec-policy: default
|
||||
|
||||
{# catalog-role: member #}
|
||||
{# catalog-zone: hamburg.ccc.de.catalog. #}
|
||||
# template for generic secondary zones
|
||||
- id: secondary
|
||||
storage: "/var/lib/knot/secondary_zones/"
|
||||
dnssec-signing: off
|
||||
|
||||
# template for zones that support dynamic updates
|
||||
- id: dynamic-primary
|
||||
storage: "/var/lib/knot/dynamic_zones/"
|
||||
zonefile-load: whole
|
||||
journal-content: changes
|
||||
dnssec-signing: on
|
||||
dnssec-policy: default
|
||||
|
||||
# template for automatically created special zones
|
||||
- id: catalog
|
||||
catalog-role: generate
|
||||
dnssec-signing: off
|
||||
storage: "/var/lib/knot/catalog_zones/"
|
||||
|
||||
# template for secondary catalog zones (they are interpreted and generate secondary zones based on their content)
|
||||
- id: catalog-secondary
|
||||
catalog-role: interpret
|
||||
dnssec-signing: off
|
||||
storage: "/var/lib/knot/secondary_zones/"
|
||||
|
||||
# templates for secondary catalog member zones
|
||||
# these templates are for zones which get spawned by catalog zones
|
||||
# unfortunately we need one template per interpreted catalog zone because it must include all information required for that zone to work as secondary
|
||||
# it reuses master and notify values from the catalog zones
|
||||
{% for i_zone in knot__catalog_secondary_zones -%}
|
||||
- id: catalog-secondary-member-{{ i_zone.domain }}
|
||||
storage: "/var/lib/knot/secondary_zones/"
|
||||
dnssec-signing: off
|
||||
master: {{ i_zone.primary }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
# define zones on this server
|
||||
|
|
@ -80,10 +140,10 @@ zone:
|
|||
notify: [ {% for i_notif in i_zone.notify_targets | default([]) %}"{{ i_notif }}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
{% endfor %}
|
||||
|
||||
# normal zones
|
||||
{% for i_zone in knot__zones -%}
|
||||
# primary zones
|
||||
{% for i_zone in knot__primary_zones -%}
|
||||
- domain: "{{ i_zone.domain }}"
|
||||
template: default
|
||||
template: primary
|
||||
notify: [ {% for i_notif in i_zone.notify_targets | default([]) %}"{{ i_notif }}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
{% if i_zone.catalog_member | default(False) -%}
|
||||
catalog-role: member
|
||||
|
|
@ -92,3 +152,39 @@ zone:
|
|||
|
||||
{% endfor %}
|
||||
|
||||
# dynamic primary zones
|
||||
{% for i_zone in knot__dynamic_zones -%}
|
||||
- domain: "{{ i_zone.domain }}"
|
||||
template: "dynamic-primary"
|
||||
notify: [ {% for i_notif in i_zone.notify_targets | default([]) %}"{{ i_notif }}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
acl: [ {% for i_acl in i_zone.acl %}"{{ i_acl }}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
{% if i_zone.catalog_member | default(False) -%}
|
||||
catalog-role: member
|
||||
catalog-zone: "{{ i_zone.catalog_member }}"
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# secondary zones
|
||||
{% for i_zone in knot__secondary_zones -%}
|
||||
- domain: "{{ i_zone.domain }}"
|
||||
template: secondary
|
||||
master: {{ i_zone.primary }}
|
||||
notify: [ {% for i_notif in i_zone.notify_targets | default([]) %}"{{ i_notif }}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
{% if i_zone.catalog_member | default(False) -%}
|
||||
catalog-role: member
|
||||
catalog-zone: "{{ i_zone.catalog_member }}"
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
# secondary catalog zones
|
||||
{% for i_zone in knot__catalog_secondary_zones -%}
|
||||
- domain: "{{ i_zone.domain }}"
|
||||
template: catalog-secondary
|
||||
catalog-template: catalog-secondary-member-{{ i_zone.domain }}
|
||||
master: {{ i_zone.primary }}
|
||||
notify: [ {% for i_notif in i_zone.notify_targets | default([]) %}"{{ i_notif }}"{% if not loop.last %}, {% endif %}{% endfor %} ]
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
# {{ ansible_managed }}
|
||||
network:
|
||||
ethernets:
|
||||
{%- for i_iface_name in ansible_facts["interfaces"] -%}
|
||||
{%- if i_iface_name != "lo" -%}
|
||||
{%- set i_iface = ansible_facts[i_iface_name] %}
|
||||
|
||||
{{ i_iface_name }}:
|
||||
match:
|
||||
macaddress: "{{ i_iface.macaddress }}"
|
||||
accept-ra: false
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
Loading…
Reference in a new issue