Exemple #1
0
async def communities(participant: Participant, ) -> None:
    """Generate Participant-specific BGP Community Lists."""
    log.info("Generating Communities for {}", participant.pretty)
    create_file_structure()

    participant_comms = template_env.get_template("participant-communities.j2")

    for rs in params.route_servers:
        result = await participant_comms.render_async(
            p=participant, now=datetime.utcnow().isoformat())
        output_file = POLICIES_DIR / rs.name / str(
            participant.asn) / "communities.ios"

        if not output_file.exists():
            output_file.touch()

        log.debug("Communities for {}\n{}", participant.pretty, result)

        with output_file.open("w+") as of:
            of.write(result)

        if verify_complete(output_file):
            log.success(
                "Generated Communities for {} at {}",
                participant.pretty,
                str(output_file),
            )
Exemple #2
0
async def route_map(participant: Participant) -> None:
    """Generate Participant-specific Route Maps."""
    log.info("Generating Route Maps for {}", participant.pretty)
    create_file_structure()
    participant_route_map = template_env.get_template(
        "participant-route-map.j2")
    for rs in params.route_servers:

        result = await participant_route_map.render_async(
            p=participant,
            rs=rs.id,
            loc=rs.loc_id,
            metro=rs.metro_id,
            now=datetime.utcnow().isoformat(),
        )
        output_file = POLICIES_DIR / rs.name / str(
            participant.asn) / "route-map.ios"

        if not output_file.exists():
            output_file.touch()

        log.debug("Route Maps for {}\n{}", participant.pretty, result)

        with output_file.open("w+") as of:
            of.write(result)

        if verify_complete(output_file):
            log.success(
                "Generated Route Maps for {} at {}",
                participant.pretty,
                str(output_file),
            )
Exemple #3
0
async def bgp(participant: Participant) -> None:
    """Generate Participant-specific BGP Configs."""
    log.info("Generating BGP Config for {}", participant.pretty)
    create_file_structure()
    max4, max6 = await max_prefixes(participant.asn)
    for rs in params.route_servers:
        output_file = POLICIES_DIR / rs.name / str(participant.asn) / "bgp.ios"

        if not output_file.exists():
            output_file.touch()

        template = template_env.get_template("participant-bgp.j2")
        result = await template.render_async(p=participant,
                                             max4=max4,
                                             max6=max6,
                                             now=datetime.utcnow().isoformat())

        log.debug("BGP Config for {}\n{}", participant.pretty, result)

        with output_file.open("w+") as of:
            of.write(result)

        if verify_complete(output_file):
            log.success(
                "Generated BGP Config for {} at {}",
                participant.pretty,
                str(output_file),
            )
Exemple #4
0
 def exposed_update_policy(self,
                           wait: int = 60) -> Generator[str, None, None]:
     """Run a policy refresh & push it to route servers."""
     log.info("Received request to update routing policies")
     loop = asyncio.new_event_loop()
     results = loop.run_until_complete(policy(wait))
     for result in results:
         yield result
Exemple #5
0
async def policy(wait: int = 0) -> Sequence[str]:
    """Generate participant policies and config files."""
    _, __, results = await asyncio.gather(generate_all(), generate_combined(),
                                          send_policies(wait))

    for result in results:
        log.info(result)

    return results
Exemple #6
0
def start_api() -> None:
    """Start the socket API server."""
    log.info("Starting Socket API")
    server = ThreadedServer(
        Updater,
        hostname="::",
        port=params.api.port,
        ipv6=True,
    )
    server.start()
Exemple #7
0
async def send_policy(route_server: RouteServer, wait: int) -> str:
    """Read a generated policy & send it to a route server."""
    result = ""
    # Optionally block pause of execution.
    if wait:
        log.info("Waiting {} seconds before proceeding with update", wait)
        time.sleep(wait)

    try:
        connection: Connection = rpyc.connect(route_server.name,
                                              params.agent.port,
                                              ipv6=True)
    except ConnectionRefusedError:
        result = "Connection refused to {}:{}".format(route_server.name,
                                                      params.agent.port)
    except gaierror:
        result = "Route Server {}:{} is unreachable".format(
            route_server.name, params.agent.port)

    # If an error has occurred, stop here & return a valuable error
    # message.
    if result:
        log.critical(result)
        await send_webhook(route_server.name, "Route Policy Error", result)
        return result

    config_file = POLICIES_DIR / route_server.name / "_all.ios"

    # Read the combined configuration file & generate a SHA256 digest of
    # its content. Then, encrypt the content, and generate another
    # SHA256 digest of the encrypted contents.
    with config_file.open("r") as f:
        payload, predigest, postdigest = create_payload(f.read())

    log.debug("Pre Encryption Digest: {}", predigest)
    log.debug("Post Encryption Digest: {}", postdigest)

    while not connection.closed:
        log.info("Sending Policy to Route Server {} ({})", route_server.name,
                 predigest)

        # Send the preflight payload digests.
        connection.root.set_digest(postdigest, predigest)

        # Send the encrypted payload.
        response = connection.root.push_policy(payload)
        result = "{}: {}".format(route_server.name, response)
        log.success(result)
        break

    # Gracefully close the connection.
    connection.close()
    await send_webhook(route_server.name, "Route Policy Update", result)
    return result
Exemple #8
0
async def generate_combined() -> None:
    """Combine generated per-participant configs into a single file per route server."""
    combined_template = template_env.get_template("combined.j2")
    (
        g_aspaths,
        g_communities,
        g_prefixes4,
        g_prefixes6,
        g_bgp,
        g_route_maps,
    ) = global_policies()

    for rs in params.route_servers:
        log.info("Generating combined config file for {}", rs.name)

        items = ["" for _ in FILE_NAMES]
        rs_path = POLICIES_DIR / rs.name
        output_file = rs_path / "_all.ios"

        for participant in params.participants:
            participant_path = rs_path / str(participant.asn)

            for i, file_name in enumerate(FILE_NAMES):
                file = participant_path / file_name
                try:
                    with file.open("r") as f:
                        items[i] += "\n" + f.read()
                except FileNotFoundError:
                    items[i] += "\n" + "! Not Found"
                    log.critical("{} does not exist.", str(file))

        communities, prefixes4, prefixes6, route_maps, bgp = items

        output = await combined_template.render_async(
            rs=rs,
            communities="\n".join((g_communities, communities)),
            aspaths=g_aspaths,
            bgp="\n".join((g_bgp, bgp)),
            prefixes4="\n".join((g_prefixes4, prefixes4)),
            prefixes6="\n".join((g_prefixes6, prefixes6)),
            route_maps="\n".join((g_route_maps, route_maps)),
            now=datetime.utcnow().isoformat(),
        )

        with output_file.open("w+") as of:
            of.write(output)
Exemple #9
0
async def prefixes(participant: Participant) -> None:
    """Generate Participant-specific Prefix Lists."""
    log.info("Generating Prefix Lists for {}", participant.pretty)
    create_file_structure()
    for rs in params.route_servers:
        async for family, render in render_prefixes(
                participant,
                max_ipv4=params.max_length.ipv4,
                max_ipv6=params.max_length.ipv6,
                template_env=template_env,
        ):
            output_file = (POLICIES_DIR / rs.name / str(participant.asn) /
                           f"prefix-list-ipv{family}.ios")

            if not output_file.exists():
                output_file.touch()

            rendered = await render

            log.debug(
                "IPv{} Prefix List for {}\n{}",
                family,
                participant.pretty,
                rendered,
            )

            with output_file.open("w") as of:
                of.write(rendered)

            if verify_complete(output_file):
                log.success(
                    "Generated IPv{} Prefix Lists for {} at {}",
                    family,
                    participant.pretty,
                    str(output_file),
                )
Exemple #10
0
 def write(message: str) -> None:
     """Log job details."""
     msg = message.strip().rstrip()
     if msg and "Jobstore" not in msg:
         log.info("Job: {}", msg)
Exemple #11
0
def test_rpc_acl() -> None:
    """Open an RPC connection and request and ACL update."""
    connection: Connection = rpyc.connect("localhost", "4801", ipv6=True)
    result = connection.root.update_acls()
    log.info(result)
Exemple #12
0
async def send_acls(  # noqa: C901 your mom's too complex
        switches: List[Switch], participants: List[Participant]) -> str:
    """Send ACLs to IX switches, only if updates are required."""
    messages: Tuple[str, ...] = ()
    # Determine if an ACL push is required.
    async for participant in requires_update(switches, participants):

        for switch in switches:

            async with AsyncClient(
                    auth=((switch.username,
                           switch.password.get_secret_value())),
                    verify=False,
            ) as client:
                p_messages: Tuple[str, ...] = ()
                p_errors: Tuple[str, ...] = ()
                acl4 = await generate_acl4(participant)
                acl6 = await generate_acl6(participant)
                push_id = (
                    f"{participant.pretty} ACL Push {datetime.utcnow().isoformat()}"
                )
                data = {
                    "id": push_id,
                    "jsonrpc": "2.0",
                    "method": "runCmds",
                    "params": {
                        "version": 1,
                        "format": "json",
                        "timestamps": False,
                        "autoComplete": False,
                        "expandAliases": False,
                        "cmds": [
                            "configure",
                            *acl4,
                            *acl6,
                        ],
                    },
                }
                log.info("Sending {} ACLs to {}", participant.pretty,
                         switch.name)
                log.debug(json.dumps(data, indent=2))

                res = await client.post(
                    f"https://{str(switch.address)}/command-api", json=data)
                res_json = res.json()

                # Handle errors returned from the switch.
                if "error" in res_json:
                    res_error: Tuple[str, ...] = ()
                    error_data = [
                        d for d in res_json["error"].get("data", [])
                        if "errors" in d
                    ]

                    for d in error_data:
                        for e in d.get("errors", []):
                            res_error += (e, )

                    if "message" in res_json["error"]:
                        res_error += (res_json["error"]["message"], )

                    p_errors += (", ".join(res_error), )

                # Handle non-error messages returned from the switch.
                elif "result" in res_json:
                    for d in [d for d in res_json["result"] if d]:
                        p_messages += (d, )

                if len(p_errors) == 0:
                    messages += ("{} ACLs updated on {}".format(
                        participant.pretty, str(switch.address)), )

                for err in p_errors:
                    msg = "Error while sending {} ACLs to {}:\n{}".format(
                        participant.pretty, str(switch.address), err)
                    log.error(msg)
                    messages += (msg, )

                for m in p_messages:
                    msg = "Response while sending {} ACLs to {}:\n{}".format(
                        participant.pretty,
                        str(switch.address),
                        m,
                    )
                    log.info(msg)
                    messages += (msg, )

    if len(messages) == 0:
        message = "No particiapant ACLs require updates."
    else:
        message = ", ".join(messages)
    await send_webhook("Detail", "Switch ACL Updates", "\n".join(messages)
                       or message)
    return message
Exemple #13
0
async def _run_test(asn: str) -> None:
    for family in (4, 6):
        prefixes = get_prefixes(asn, family)
        log.info("IPv{} prefixes for {}:", str(family), f"AS{asn}")
        async for prefix in prefixes:
            log.info(prefix)
Exemple #14
0
 def exposed_update_acls(self) -> str:
     """Generate & Update Switch ACLs if needed."""
     log.info("Received request to update switch ACLs")
     loop = asyncio.new_event_loop()
     result = loop.run_until_complete(acls())
     return result
Exemple #15
0
async def requires_update(  # noqa: C901 your mom's too complex
        switches: List[Switch],
        participants: List[Participant]) -> AsyncGenerator[Participant, None]:
    """Determine if an ACL needs to be updated.

    Pull each switch's current running config and extract ACL sections.

    Generate new ACLs based on current policy standard.

    Compare the current configuration state vs. what would be deployed
    and if there are any differences, mark that participant's ACLs for
    the re-deployment.
    """
    requires_update = set()
    for switch in switches:
        # Get current running-configuration.
        current_config = await get_current_config(switch)

        # Parse IPv4 access-lists
        current_acl4 = await parse_acls("ip", current_config)

        # Parse IPv6 access-lists
        current_acl6 = await parse_acls("ipv6", current_config)

        for participant in participants:
            # Generate new IPv4 access-lists based on current policy.
            new_acl4 = await generate_acl4(participant)

            # Generate new IPv6 access-lists based on current policy.
            new_acl6 = await generate_acl6(participant)

            for afi in ((current_acl4, new_acl4), (current_acl6, new_acl6)):
                current_acls, new = afi
                matched = None

                for i, acl in enumerate(current_acls):
                    # Match the current ACL to the new based on the
                    # first line, which contains the participant's ASN.
                    if acl[0] == new[0]:
                        matched = i
                        break

                if matched is None:
                    # If there was no match, this means the ACL doesn't
                    # exist on the switch and should be deployed.
                    log.info(
                        "No ACL for {} exists on {}",
                        participant.pretty,
                        str(switch.address),
                    )
                    requires_update.add(participant)

                else:
                    # Otherwise, confirm the ACL from the switch matches
                    # the ACL generated from policy.
                    for i, new_acl in enumerate(new):

                        # Compare each line for differences.
                        if new_acl != current_acls[matched][i]:
                            log.warning(
                                "New ACL entry '{}' does not match current '{}' for {}",
                                new_acl,
                                acl[i],
                                participant.pretty,
                            )

                            # If there is a difference, mark the
                            # participant for re-generation on first
                            # match.
                            requires_update.add(participant)
                            break

    if len(requires_update) == 0:
        log.info("No particiapant ACLs require updates.")

    for participant in requires_update:
        yield participant