Beispiel #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),
            )
Beispiel #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),
            )
Beispiel #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),
            )
Beispiel #4
0
async def run_whois(query: str) -> AsyncGenerator[str, None]:
    """Open raw socket to IRRd host and execute query."""

    # Open the socket to IRRd
    log.debug("Opening connection to IRRd server")
    reader, writer = await asyncio.open_connection("rr.ntt.net", port=43)

    # Send the query
    writer.write(query.encode())
    await writer.drain()
    # Read the response
    response = b""
    while True:
        data = await reader.read(128)
        if data:
            response += data
        else:
            log.debug("Closing connection to IRRd server")
            writer.close()
            break

    if writer.can_write_eof():
        writer.write_eof()
    items = ",".join(response.decode().split()[1:-1]).split(",")
    for item in (i for i in items if i):
        yield item
Beispiel #5
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
Beispiel #6
0
async def send_webhook(title: str, summary: str, message: str) -> None:
    """Send a Slack webhook."""

    generated = generate_policy_block(title, summary, message)

    log.debug("Generated Slack hook:\n{}", generated)
    try:
        async with AsyncClient(base_url="https://hooks.slack.com") as client:
            await client.post(
                str(params.slack.webhook.path),
                json=generated,
            )
    except Exception as err:
        log.error(str(err))
Beispiel #7
0
async def get_as_set(asn: str) -> Sequence[str]:
    """Search PeeringDB for an entry matching an ASN and return its IRR AS_Set."""
    result = []
    async with AsyncClient(
            http2=True,
            verify=True,
            base_url="https://peeringdb.com",
            headers={"Accept": "application/json"},
    ) as client:
        log.debug("Getting max prefixes for AS{}", asn)
        res = await client.get("/api/net", params={"asn__contains": asn})
        res.raise_for_status()
        for data in res.json()["data"]:
            if "asn" in data and str(data["asn"]) == asn:
                log.debug("Matched AS{} to {}", str(asn), data["name"])
                log.debug(
                    "AS{} PeeringDB Org ID {}, last updated {}",
                    str(asn),
                    str(data["org_id"]),
                    data["updated"],
                )
                as_set = data.get("irr_as_set", "")
                if as_set != "":
                    result = as_set.split(" ")
                    log.debug("Found AS-Set(s) {} for {}", result,
                              data["name"])
                break
    return result
Beispiel #8
0
async def max_prefixes(asn: int) -> Tuple[int, int]:
    """Search PeeringDB for an entry matching an ASN and return its max prefixes."""
    prefixes = (200, 20)
    async with AsyncClient(
            http2=True,
            verify=True,
            base_url="https://peeringdb.com",
            headers={"Accept": "application/json"},
    ) as client:
        log.debug("Getting max prefixes for AS{}", str(asn))
        res = await client.get("/api/net", params={"asn__contains": asn})
        res.raise_for_status()
        for data in res.json()["data"]:
            if "asn" in data and data["asn"] == asn:
                log.debug("Matched AS{} to {}", str(asn), data["name"])
                log.debug(
                    "AS{} PeeringDB Org ID {}, last updated {}",
                    str(asn),
                    str(data["org_id"]),
                    data["updated"],
                )
                prefixes = (
                    data.get("info_prefixes4", 200),
                    data.get("info_prefixes6", 20),
                )
    return prefixes
Beispiel #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),
                )
Beispiel #10
0
def create_file_structure() -> bool:
    """Gracefully create output policy file structure."""
    if not POLICIES_DIR.exists():
        log.debug("Creating {}", str(POLICIES_DIR))
        POLICIES_DIR.mkdir()
    for rs in params.route_servers:
        rs_dir = POLICIES_DIR / rs.name
        if not rs_dir.exists():
            log.debug("Creating {}", str(rs_dir))
            rs_dir.mkdir()
        for participant in params.participants:
            participant_dir = rs_dir / str(participant.asn)
            if not participant_dir.exists():
                log.debug("Creating {}", str(participant_dir))
                participant_dir.mkdir()
    return True
Beispiel #11
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