diff --git a/inventories/chaosknoten/host_vars/auth-dns.sops.yaml b/inventories/chaosknoten/host_vars/auth-dns.sops.yaml index 49dc4880..78832249 100644 --- a/inventories/chaosknoten/host_vars/auth-dns.sops.yaml +++ b/inventories/chaosknoten/host_vars/auth-dns.sops.yaml @@ -1,14 +1,9 @@ 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__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] - - id: ENC[AES256_GCM,data:Dulyfbyd0yrEX2gUm/YA,iv:VmSUtH5aryl7Vhq8TDj7anRASqNZaN77usVI+Rd8SVc=,tag:1ZJmSHHufQr1+DcnKYyY9w==,type:str] - secret: ENC[AES256_GCM,data:zlEdYedWmlW8mTLu+cUejyjlqLcx14Bz4D20pPa21UagtCH7GYeCldmC5sM=,iv:+TvHyfRgCscza04CtLGuRIkt30KLVRx01GKEq0Dqkvw=,tag:mX/MrOePWjJ9JwiGP6TQQw==,type:str] - algorithm: ENC[AES256_GCM,data:r7uojDN+o1yYgaY=,iv:KBWKZbd+5qZstuMh6gseq/vC86UB/l9m4wVZpA4usPk=,tag:gfU3asmwOIgxJQsqPG712g==,type:str] +knot__dnssec_key_secret: ENC[AES256_GCM,data:WPFTLyJIttFtqqTZV2fGN0Tt1vRS318TGmd2YqNzYisE3TBi6Z2aClxuYh56Q+j7TUQwCvga3jd5w017sEz3kA==,iv:umaFHBCy9AZgNFv7uXLCtO0o/NZDAZ1QNg5DcGHWEW8=,tag:oR92C1Uj5iXU9L02MqzGSQ==,type:str] sops: age: - - enc: | + - recipient: age18zgt4y2sd75hxnpe333zz39048ctxpr0q8a3uqh3jajjkyawsdrq8yg5ve + enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArVUxoOFhZdi9PSkZlU3pw VW0vNVRTSG9EamUvVGR0dnN6ajcwTXdlQkdZCkFGVWxPcTNSOGtJdlpSMXJxOFBX @@ -16,9 +11,8 @@ sops: SHhROG5nMGp2d3dwWVJsZk55TW93dU0KnW2ZhU6OhwwPBIxcO1xP+W8DV5Obj6ov Hgb8MQ6i9FlhAN/P8onBsqvbh0ttBEFQ6aRJj5njKs/MMfKUk9Q25g== -----END AGE ENCRYPTED FILE----- - recipient: age18zgt4y2sd75hxnpe333zz39048ctxpr0q8a3uqh3jajjkyawsdrq8yg5ve - lastmodified: "2026-09-11T08:41:08Z" - mac: ENC[AES256_GCM,data:Kp+FNfKFSD1SGgHS7xc51CGqugyAQlTHqI1bxQxuGLht7dTdbltu8llyyGSwYuToAeUAgBKi+Nv4OQ7qM3nQ/1I3IjUJ6t3G3LNbOfOe6h4cF5wb2pV8NnpzjpwEgOMVI6dzCbW/hRqND8JrgSgnkPjM4WyOtiz8+N7evY8qAOs=,iv:kOkxsVfuVO5/XJBmnQKZm8mFVoQlUZKT51TUUAGij/8=,tag:8i59H3tvwUvSBU0LWLsdbg==,type:str] + 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] pgp: - created_at: "2026-05-20T02:08:47Z" enc: |- @@ -191,4 +185,4 @@ sops: -----END PGP MESSAGE----- fp: 41FFAF3D519CF5C039FBD8414BCC213729AF0E49 unencrypted_suffix: _unencrypted - version: 3.13.3 + version: 3.12.1 diff --git a/inventories/chaosknoten/host_vars/auth-dns.yaml b/inventories/chaosknoten/host_vars/auth-dns.yaml index e481a4e6..7d220bcb 100644 --- a/inventories/chaosknoten/host_vars/auth-dns.yaml +++ b/inventories/chaosknoten/host_vars/auth-dns.yaml @@ -3,23 +3,18 @@ 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" ] - via: [ "2a00:14b0:4200:3000:124::1", "212.12.48.124" ] - key: "xfr-ccchh-cccfr" knot__catalog_zones: - domain: "hamburg.ccc.de.catalog." notify_targets: [ "erfadns.ber.ccc.de" ] -knot__primary_zones: +knot__zones: - domain: "hh.ccc.de." catalog_member: "hamburg.ccc.de.catalog." notify_targets: [ "erfadns.ber.ccc.de" ] @@ -53,7 +48,3 @@ knot__primary_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 572abe28..50a3ffb5 100644 --- a/roles/knot/defaults/main.yaml +++ b/roles/knot/defaults/main.yaml @@ -1,11 +1,2 @@ --- -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 61a529b1..40a58238 100644 --- a/roles/knot/meta/argument_specs.yaml +++ b/roles/knot/meta/argument_specs.yaml @@ -2,15 +2,14 @@ argument_specs: main: options: - knot__log_level: + knot__dnssec_key_id: + description: The id of the TSIG key which knot will use for zone transfer signing type: str - required: false - - knot__deploy_prometheus_exporter: - type: bool - required: false - description: Whether a prometheus exporter for knot should also be installed - + 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 knot__remotes: description: - A list of definitions for remote nameservers that are used for different purposes @@ -26,19 +25,11 @@ 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: false + required: true options: domain: type: str @@ -47,12 +38,11 @@ argument_specs: type: list elements: str required: false - - knot__primary_zones: + knot__zones: description: A list of user zones that will be served by knot type: list elements: dict - required: false + required: true options: domain: type: str @@ -67,105 +57,3 @@ 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 fe4be107..e79143fc 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__primary_zones }}" + loop: "{{ knot__zones }}" loop_control: label: "{{ item.domain }}" vars: @@ -34,3 +34,17 @@ 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 145dc582..bdf5cf78 100644 --- a/roles/knot/tasks/main.yaml +++ b/roles/knot/tasks/main.yaml @@ -1,7 +1,4 @@ --- -- 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 +- 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] diff --git a/roles/knot/templates/knot.conf.j2 b/roles/knot/templates/knot.conf.j2 index 03c46ffe..45a0f8d9 100644 --- a/roles/knot/templates/knot.conf.j2 +++ b/roles/knot/templates/knot.conf.j2 @@ -9,17 +9,15 @@ server: log: - target: syslog - any: {{ knot__log_level }} + any: info database: storage: "/var/lib/knot" key: - {% for i_key in knot__keys -%} - - id: "{{ i_key.id }}" - algorithm: "{{ i_key.algorithm | default("hmac-sha256") }}" - secret: "{{ i_key.secret }}" - {% endfor %} + - id: {{ knot__dnssec_key_id }} + algorithm: hmac-sha512 + secret: "{{ knot__dnssec_key_secret }}" remote: # static, external and public remote used for DNSSEC KSK checking @@ -30,13 +28,6 @@ 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 %} @@ -55,33 +46,10 @@ 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 primary zones - - id: primary + # template for general-purpose user zones + - id: default storage: "/etc/knot/zones" file: "%s.zone" semantic-checks: on @@ -89,45 +57,17 @@ template: zonefile-load: difference-no-serial serial-policy: dateserial journal-content: all + default-ttl: 7200 dnssec-signing: on dnssec-policy: default - # 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 + {# catalog-role: member #} + {# catalog-zone: hamburg.ccc.de.catalog. #} # 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 @@ -140,10 +80,10 @@ zone: notify: [ {% for i_notif in i_zone.notify_targets | default([]) %}"{{ i_notif }}"{% if not loop.last %}, {% endif %}{% endfor %} ] {% endfor %} - # primary zones - {% for i_zone in knot__primary_zones -%} + # normal zones + {% for i_zone in knot__zones -%} - domain: "{{ i_zone.domain }}" - template: primary + template: default 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 @@ -152,39 +92,3 @@ 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 new file mode 100644 index 00000000..bad31a5a --- /dev/null +++ b/roles/knot/templates/netplan-disable-ra.yaml @@ -0,0 +1,13 @@ +# {{ 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 %}