diff --git a/inventories/chaosknoten/host_vars/auth-dns.sops.yaml b/inventories/chaosknoten/host_vars/auth-dns.sops.yaml index 78832249..fbf06b66 100644 --- a/inventories/chaosknoten/host_vars/auth-dns.sops.yaml +++ b/inventories/chaosknoten/host_vars/auth-dns.sops.yaml @@ -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 diff --git a/inventories/chaosknoten/host_vars/auth-dns.yaml b/inventories/chaosknoten/host_vars/auth-dns.yaml index 7d220bcb..035e0328 100644 --- a/inventories/chaosknoten/host_vars/auth-dns.yaml +++ b/inventories/chaosknoten/host_vars/auth-dns.yaml @@ -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" diff --git a/roles/knot/defaults/main.yaml b/roles/knot/defaults/main.yaml index 50a3ffb5..572abe28 100644 --- a/roles/knot/defaults/main.yaml +++ b/roles/knot/defaults/main.yaml @@ -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: [ ] diff --git a/roles/knot/meta/argument_specs.yaml b/roles/knot/meta/argument_specs.yaml index 40a58238..61a529b1 100644 --- a/roles/knot/meta/argument_specs.yaml +++ b/roles/knot/meta/argument_specs.yaml @@ -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 diff --git a/roles/knot/tasks/02-configure.yaml b/roles/knot/tasks/02-configure.yaml index e79143fc..fe4be107 100644 --- a/roles/knot/tasks/02-configure.yaml +++ b/roles/knot/tasks/02-configure.yaml @@ -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= diff --git a/roles/knot/tasks/main.yaml b/roles/knot/tasks/main.yaml index bdf5cf78..145dc582 100644 --- a/roles/knot/tasks/main.yaml +++ b/roles/knot/tasks/main.yaml @@ -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 diff --git a/roles/knot/templates/knot.conf.j2 b/roles/knot/templates/knot.conf.j2 index 45a0f8d9..03c46ffe 100644 --- a/roles/knot/templates/knot.conf.j2 +++ b/roles/knot/templates/knot.conf.j2 @@ -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 %} + diff --git a/roles/knot/templates/netplan-disable-ra.yaml b/roles/knot/templates/netplan-disable-ra.yaml deleted file mode 100644 index bad31a5a..00000000 --- a/roles/knot/templates/netplan-disable-ra.yaml +++ /dev/null @@ -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 %}