Compare commits

..

2 commits

Author SHA1 Message Date
Timotej Lazar 036f7c8b74 Support custom allowed_ips field for services
Like allowed_prefixes, but for single IP addresses. Currently used
just for DHCP server to allow (only) packets from relays.
2024-08-03 11:44:03 +02:00
Timotej Lazar 01a27e45ce dnsmasq: add script for dynamic DNS updates 2024-08-02 12:08:32 +02:00
8 changed files with 101 additions and 23 deletions

View file

@ -1,5 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
import ipaddress
import os import os
import pynetbox import pynetbox
@ -38,12 +39,21 @@ class FilterModule(object):
def allowed_prefixes(self, service): def allowed_prefixes(self, service):
'''Return a list of allowed IP prefixes for the given service''' '''Return a list of allowed IP prefixes for the given service'''
# Depending on how service was obtained, actual data might be nested under custom_fields.
if 'custom_fields' in service: if 'custom_fields' in service:
service = service['custom_fields'] service = service['custom_fields']
def ip2str(address, single=False):
if single: # 1.2.3.4/24 → 1.2.3.4
address = ipaddress.ip_interface(address).ip
return str(ipaddress.ip_interface(address))
if ips := service.get('allowed_ips'):
yield from (ip2str(ip['address'], single=True) for ip in ips)
if prefixes := service.get('allowed_prefixes'): if prefixes := service.get('allowed_prefixes'):
yield from self.nb.ipam.prefixes.filter(id=[prefix['id'] for prefix in prefixes]) yield from (ip2str(e.prefix) for e in self.nb.ipam.prefixes.filter(id=[prefix['id'] for prefix in prefixes]))
if vlans := service.get('allowed_vlans'): if vlans := service.get('allowed_vlans'):
yield from self.nb.ipam.prefixes.filter(vlan_id=[vlan['id'] for vlan in vlans]) yield from (ip2str(e.prefix) for e in self.nb.ipam.prefixes.filter(vlan_id=[vlan['id'] for vlan in vlans]))
if clusters := service.get('allowed_clusters'): if clusters := service.get('allowed_clusters'):
for device in self.nb.dcim.devices.filter(cluster_id=[cluster['id'] for cluster in clusters]): for device in self.nb.dcim.devices.filter(cluster_id=[cluster['id'] for cluster in clusters]):
yield from self.nb.ipam.ip_addresses.filter(role='loopback', device_id=device.id) yield from (ip2str(e.address) for e in self.nb.ipam.ip_addresses.filter(role='loopback', device_id=device.id))

View file

@ -4,18 +4,16 @@ table inet filter {
{% for service in services %} {% for service in services %}
{% set prefixes = service | allowed_prefixes %} {% set prefixes = service | allowed_prefixes %}
{% set prefixes4 = prefixes | selectattr('family.value', '==', 4) | map('string') %}
{% set prefixes6 = prefixes | selectattr('family.value', '==', 6) | map('string') %}
{% set ports = service.ports | compact_numlist %} {% set ports = service.ports | compact_numlist %}
{% if 'name' in service %} {% if 'name' in service %}
# service {{ service.name }} # service {{ service.name }}
{% endif %} {% endif %}
{% if prefixes4 or prefixes6 %} {% if prefixes %}
{% if prefixes4 %} {% if prefixes | ipv4 %}
ip saddr { {{ prefixes4 | join(', ') }} } {{ service.protocol.value }} dport { {{ ports }} } accept ip saddr { {{ prefixes | ipv4 | join(', ') }} } {{ service.protocol.value }} dport { {{ ports }} } accept
{% endif %} {% endif %}
{% if prefixes6 %} {% if prefixes | ipv6 %}
ip6 saddr { {{ prefixes6 | join(', ') }} } {{ service.protocol.value }} dport { {{ ports }} } accept ip6 saddr { {{ prefixes | ipv6 | join(', ') }} } {{ service.protocol.value }} dport { {{ ports }} } accept
{% endif %} {% endif %}
{% else %} {% else %}
tcp dport { {{ ports }} } accept tcp dport { {{ ports }} } accept

View file

@ -56,15 +56,13 @@ table inet filter {
{% for service in cluster_services %} {% for service in cluster_services %}
{% set prefixes = service | allowed_prefixes %} {% set prefixes = service | allowed_prefixes %}
{% set prefixes4 = prefixes | selectattr('family.value', '==', 4) | map('string') %}
{% set prefixes6 = prefixes | selectattr('family.value', '==', 6) | map('string') %}
{% set ports = service.ports | compact_numlist %} {% set ports = service.ports | compact_numlist %}
# service {{ service.name }} # service {{ service.name }}
{% if prefixes4 %} {% if prefixes | ipv4 %}
ip saddr { {{ prefixes4 | join(', ') }} } tcp dport { {{ ports }} } accept ip saddr { {{ prefixes | ipv4 | join(', ') }} } tcp dport { {{ ports }} } accept
{% endif %} {% endif %}
{% if prefixes6 %} {% if prefixes | ipv6 %}
ip6 saddr { {{ prefixes6 | join(', ') }} } tcp dport { {{ ports }} } accept ip6 saddr { {{ prefixes | ipv6 | join(', ') }} } tcp dport { {{ ports }} } accept
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View file

@ -2,6 +2,32 @@
package: package:
name: name:
- dnsmasq - dnsmasq
- bind-tools
- krb5
- py3-pexpect
- name: Configure kerberos
template:
dest: /etc/krb5.conf
src: krb5.conf.j2
- name: Init kerberos keytab
expect:
command: ktutil
responses:
'.*:':
- 'add_entry -password -p {{ password.ldap_user }} -k 1 -e aes256-cts-hmac-sha1-96'
- '{{ password.ldap_pass }}'
- 'write_kt /etc/krb5.keytab'
- 'exit'
args:
creates: /etc/krb5.keytab
- name: Copy DNS updater script
template:
dest: "/usr/local/bin/dns-update"
src: "dns-update.j2"
mode: 0700
- name: Configure dnsmasq - name: Configure dnsmasq
template: template:
@ -12,5 +38,4 @@
- 10-ranges.conf - 10-ranges.conf
notify: restart dnsmasq notify: restart dnsmasq
# TODO DNS update
# TODO netboot config # TODO netboot config

View file

@ -9,3 +9,5 @@ dhcp-proxy
dhcp-option = option:dns-server,{{ dns | join(',') }} dhcp-option = option:dns-server,{{ dns | join(',') }}
dhcp-option = option:ntp-server,{{ ntp | join(',') }} dhcp-option = option:ntp-server,{{ ntp | join(',') }}
dhcp-script = /usr/local/bin/dns-update

View file

@ -0,0 +1,29 @@
#!/bin/sh
domain={{ domain }}
ldap_user={{ password.ldap_user }}
ttl=3600
address="${3}"
case "${1}" in
add)
host="${4}"
kinit -k "${ldap_user}"
nsupdate -g <<EOF
update add ${host}.${domain} ${ttl} A ${address}
send
EOF
;;
old)
if [ -n "${DNSMASQ_OLD_HOSTNAME}" -a -n "${DNSMASQ_SUPPLIED_HOSTNAME}" ] ; then
kinit -k "${ldap_user}"
nsupdate -g <<EOF
update del ${DNSMASQ_OLD_HOSTNAME}.${domain}
update add ${DNSMASQ_SUPPLIED_HOSTNAME}.${domain} ${ttl} A ${address}
send
EOF
fi
;;
# TODO del, probably
esac

View file

@ -0,0 +1,18 @@
[libdefaults]
dns_lookup_realm = false
ticket_lifetime = 24h
renew_lifetime = 7d
#forwardable = true
rdns = false
default_realm = {{ domain | upper }}
[realms]
{{ domain | upper }} = {
{% for server in dns %}
kdc = {{ server }}
{% endfor %}
}
[domain_realm]
.{{ domain }} = {{ domain | upper }}
{{ domain }} = {{ domain | upper }}

View file

@ -10,14 +10,12 @@ IN ACCEPT -source {{ nodes | map('device_address') | flatten | selectattr('famil
IN ACCEPT -source {{ nodes | map('device_address') | flatten | selectattr('family.value', '==', 6) | map(attribute='address') | join(',') }} # my cluster IN ACCEPT -source {{ nodes | map('device_address') | flatten | selectattr('family.value', '==', 6) | map(attribute='address') | join(',') }} # my cluster
{% for service in cluster_services %} {% for service in cluster_services %}
{% set prefixes = service | allowed_prefixes %} {% set prefixes = service | allowed_prefixes %}
{% set prefixes4 = prefixes | selectattr('family.value', '==', 4) | map('string') %}
{% set prefixes6 = prefixes | selectattr('family.value', '==', 6) | map('string') %}
{% set ports = service.ports | compact_numlist(range_delimiter=':') %} {% set ports = service.ports | compact_numlist(range_delimiter=':') %}
{% if prefixes4 %} {% if prefixes | ipv4 %}
IN ACCEPT -source {{ prefixes4 | join(',') }} -p {{ service.protocol.value }} -dport {{ ports }} # {{ service.name }} IN ACCEPT -source {{ prefixes | ipv4 | join(',') }} -p {{ service.protocol.value }} -dport {{ ports }} # {{ service.name }}
{% endif %} {% endif %}
{% if prefixes6 %} {% if prefixes | ipv6 %}
IN ACCEPT -source {{ prefixes6 | join(',') }} -p {{ service.protocol.value }} -dport {{ ports }} # {{ service.name }} IN ACCEPT -source {{ prefixes | ipv6 | join(',') }} -p {{ service.protocol.value }} -dport {{ ports }} # {{ service.name }}
{% endif %} {% endif %}
{% endfor %} {% endfor %}