Source code for univention.management.console.modules.ipchange

#!/usr/bin/python3
#
# Univention Management Console
#  module: ipchange
#
# SPDX-FileCopyrightText: 2012-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only

import ipaddress
from urllib.parse import urlparse

from ldap.filter import filter_format

import univention.admin.modules
import univention.admin.uldap
from univention.management.console.base import Base
from univention.management.console.config import ucr
from univention.management.console.error import BadRequest
from univention.management.console.log import MODULE
from univention.management.console.modules.decorators import simple_response


univention.admin.modules.update()


[docs] class Instance(Base):
[docs] @simple_response def change(self, role: str, ip: str, netmask: str, oldip: str | None = None) -> dict: """ Return a dict with all necessary values for ipchange read from the current status of the system. """ # ignore link local addresses (no DHCP address received) network = ipaddress.IPv4Network(f'{ip}/{netmask}', False) if network.is_link_local: MODULE.error('Ignore link local address change.') return lo, position = univention.admin.uldap.getAdminConnection() hmodule = univention.admin.modules.get('dns/host_record') cmodule = univention.admin.modules.get(f'computers/{role}') # check if already used res = univention.admin.modules.lookup(hmodule, None, lo, scope='sub', filter=filter_format('aRecord=%s', (ip,))) used_by = [entry['name'] for entry in res if 'name' in entry] if used_by: raise BadRequest(f'The IP address is already in use by host record(s) for: {", ".join(used_by)}') # do we have a forward zone for this IP address? if oldip and oldip != ip: fmodule = univention.admin.modules.get('dns/forward_zone') for forwardobject in univention.admin.modules.lookup(fmodule, None, lo, scope='sub', superordinate=None, filter=filter_format('(aRecord=%s)', (oldip,))): forwardobject.open() forwardobject['a'].remove(oldip) forwardobject['a'].append(ip) forwardobject.modify() # remove old DNS reverse entries with old IP server = cmodule.object(None, lo, position, self.user_dn) server.open() current_ips = server['ip'] for entry in server['dnsEntryZoneReverse']: if entry[1] in current_ips: server['dnsEntryZoneReverse'].remove(entry) # change IP server['ip'] = ip MODULE.info("Change IP to %s", ip) server.modify() # do we have a new reverse zone for this IP address? rmodule = univention.admin.modules.get('dns/reverse_zone') parts = network.network_address.exploded.split('.') while parts[-1] == '0': parts.pop() while parts: subnet = '.'.join(parts) parts.pop() filter = filter_format('(subnet=%s)', (subnet,)) reverseobject = univention.admin.modules.lookup(rmodule, None, lo, scope='sub', superordinate=None, filter=filter) if reverseobject: server = cmodule.object(None, lo, position, self.user_dn) server.open() server['dnsEntryZoneReverse'].append([reverseobject[0].dn, ip]) server.modify() break # Change ucs-sso entry # FIXME: this should be done for UCS-in-AD domains as well! ucr.load() sso_uri = ucr.get('ucs/server/sso/uri').lower() sso_fqdn = urlparse(sso_uri).netloc if ucr.is_true('keycloak/server/sso/autoregistraton', True) and sso_fqdn: fmodule = univention.admin.modules.get('dns/forward_zone') forwardobjects = univention.admin.modules.lookup(fmodule, None, lo, scope='sub', superordinate=None, filter=None) for forwardobject in forwardobjects: zone = forwardobject.get('zone') # check case insenstive. Mostly necessary for keycloak if not sso_fqdn.endswith(zone.lower()): continue sso_name = sso_fqdn[:-(len(zone) + 1)] for current_ip in current_ips: records = univention.admin.modules.lookup(hmodule, None, lo, scope='sub', superordinate=forwardobject, filter=filter_format('(&(relativeDomainName=%s)(aRecord=%s))', (sso_name, current_ip))) for record in records: record.open() if oldip in record['a']: record['a'].remove(oldip) record['a'].append(ip) record.modify()