Mini Shell

Direktori : /proc/self/root/opt/maint/bin/
Upload File :
Current File : //proc/self/root/opt/maint/bin/fix_etc_ips.py

#!/opt/imh-python/bin/python3
"""Scans /etc/ips for IPs with incorrect or missing subnet masks/broadcasts.
Also searches for IPs in the file which are not from the correct network"""
from argparse import ArgumentParser
from configparser import ConfigParser
import logging
import os
import sys
import json
from collections import Counter
import subprocess
from netaddr import IPNetwork, IPAddress
from netaddr.core import AddrFormatError
import rads


def iter_v4_prefixes():
    """Yield subnet (IPNetwork) and zone (str) for each net in imh/whh"""
    with open('/opt/maint/etc/netbox.json', encoding='utf-8') as file:
        data: dict[str, dict] = json.load(file)['ipv4']['prefixes']
    for tenant, site_ips in data.items():
        if tenant not in ('imh', 'whh'):
            continue
        for site, nets in site_ips.items():
            for net in nets:
                try:
                    ipnet = IPNetwork(net)
                except AddrFormatError:
                    logging.error('Could not parse subnet: %r', net)
                    continue
                yield ipnet, site


def get_zone() -> str:
    """Return the value of salt[imh.zone]() which is saved to maint.ini"""
    conf = ConfigParser(strict=True)
    if not conf.read('/opt/maint/etc/maint.ini'):
        raise FileNotFoundError('/opt/maint/etc/maint.ini')
    return conf.get('net', 'zone')


def find(v4_prefixes: dict[IPNetwork, str], addr: str) -> tuple[IPNetwork, str]:
    """Find the subnet and zone an IP is from"""
    try:
        ipaddr = IPAddress(addr)
    except AddrFormatError as exc:
        raise ValueError(f'Could not parse IP: {addr!r}') from exc
    matches = {}
    for net, site in v4_prefixes.items():
        if ipaddr in net:
            matches[net] = site
    if not matches:
        raise KeyError(f'Could not find subnet for {addr!r}')
    smallest_net = sorted(matches, key=len)[0]
    return smallest_net, matches[smallest_net]


def send_str(bad_ips):
    """Send an STR ticket for an IP assigned from the wrong block"""
    logging.info('Sending STR for bad IPs: %r', bad_ips)
    body = """
The following IP(s) on this server appear to be assigned from the wrong datacenter's IP blocks.

For each,

* clear the IP from IMHSC

* check if it has a customer on it and fix / contact as necessary

* remove the IP from the server

IP(s):
    """.strip()
    rads.send_email(
        to_addr='str@imhadmin.net',
        subject='Bad IP(s) in /etc/ips',
        body='{}\n\n{}'.format(body, '\n'.join(bad_ips)),
    )


def parse_args():
    """Parse sys.argv"""
    parser = ArgumentParser(description=__doc__)
    parser.add_argument('--noop', action='store_true')
    return parser.parse_args()


def main():
    """Main program logic: Find errors in /etc/ips"""
    noop: bool = parse_args().noop
    # cron config appends stdout/err to /var/log/maint/fix_etc_ips.log
    rads.setup_logging(path=None, loglevel=logging.INFO, print_out='stdout')
    v4_prefixes = dict(iter_v4_prefixes())
    local_zone = get_zone()
    with open('/etc/ips', encoding='ascii') as handle:
        etc_ips = handle.read().split()
    fixed = False  # whether we changed /etc/ips
    wrong_zone = []  # any IPs we found asigned from the wrong block
    for index, ip_row in enumerate(etc_ips.copy()):
        if ip_row.startswith('169.'):
            continue
        addr = ip_row.split(':', 1)[0]
        try:
            net, site = find(v4_prefixes, addr)
        except (KeyError, ValueError) as exc:
            logging.error(exc)
            continue
        if site != local_zone:
            # this IP was assigned from the wrong block
            logging.warning(
                '%s was assigned from the wrong zone (%s)', addr, site
            )
            wrong_zone.append(addr)
            # Don't proceed to editing its mask - it's already likely causing
            # routing problems for this server. Don't risk making it worse.
            continue
        correct = f"{addr}:{net.netmask}:{net.broadcast}"
        if correct != ip_row:
            # either the netmask or broadcast was wrong
            logging.info('Fixing %r -> %r', ip_row, correct)
            fixed = True
            etc_ips[index] = correct
    if dupes := [k for k, v in Counter(etc_ips).items() if v > 1]:
        logging.info('Removing duplicates: %r', dupes)
        etc_ips = list(set(etc_ips))
        fixed = True
    if noop:
        return
    if fixed:  # we fixed at least one line in /etc/ips
        write_etc_ips(etc_ips)
        restart_ipaliases()
    if wrong_zone:  # at least one IP was assigned from the wrong zone in IMHSC
        send_str(wrong_zone)


def write_etc_ips(etc_ips):
    """Safely write to /etc/ips"""
    with open('/etc/ips.fixing', 'w', encoding='ascii') as handle:
        handle.write('\n'.join(etc_ips))
        handle.write('\n')
    os.rename('/etc/ips.fixing', '/etc/ips')


def restart_ipaliases():
    """Restart ipaliases to load subinterfaces from the IPs in /etc/ips"""
    cmd = ['/usr/local/cpanel/scripts/restartsrv_ipaliases']
    try:
        subprocess.check_call(cmd)
    except (subprocess.CalledProcessError, OSError):
        logging.critical('error restarting ipaliases')
        sys.exit(2)


if __name__ == '__main__':
    main()

Zerion Mini Shell 1.0