예제 #1
0
def exit_gracefully(sig, frame) -> None:
    """Signal handler that tries to warn the C&C server that the node is
    shutting down before it does so."""

    print("Exiting...")

    ip = config.get('NAT_IP', config['IP'])
    port = config.get('NAT_PORT', config['PORT'])

    signature = signatures.new_signature(
        config['C2_SECRET'],
        "DELETE",
        f"/environments/{ip}/{port}")
    authorization_content = (
        signatures.new_authorization_header("Node", signature))

    try:
        resp = rq.delete(
            f"{config['C2_URL']}/environments/{ip}/{port}",
            headers={'Authorization': authorization_content})
        resp.raise_for_status()
    except rq.exceptions.ConnectionError:
        print("Could not contact Command and Control server before exiting.")
    except Exception:
        print(resp.json()['error'])
    finally:
        sys.exit()
예제 #2
0
def install(password: str, ip: str, port: int, packages: List[str]):
    """Install the given PACKAGES in the environment at IP:PORT."""

    prepared = requests.Request("PATCH",
                                f"{C2_URL}/environments/{ip}/{port}/installed",
                                json=packages).prepare()

    digest = b64encode(sha256(prepared.body).digest()).decode()
    prepared.headers['Digest'] = f"sha-256={digest}"

    headers = ['Digest']
    signature = signatures.new_signature(
        password.encode(),
        "PATCH",
        f"/environments/{ip}/{port}/installed",
        signature_headers=headers,
        header_recoverer=lambda h: prepared.headers.get(h))
    prepared.headers['Authorization'] =\
        signatures.new_authorization_header("Client", signature, headers)

    try:
        resp = requests.Session().send(prepared)
    except requests.exceptions.ConnectionError:
        click.echo("Connection refused.")
    else:
        if resp.status_code in {400, 401, 404, 415, 500, 502, 504}:
            click.echo(resp.json()['error'])
        elif resp.status_code != 204:
            click.echo("Unexpected response from Command and Control Sever.")
예제 #3
0
def remove_available_packages(password: str, packages: List[str]):
    """Delete the given top level PACKAGES from the C&C server."""

    key = password.encode()
    for pack in packages:
        signature = signatures.new_signature(key, "DELETE",
                                             f"/test_sets/{pack}")
        auth_content = signatures.new_authorization_header("Client", signature)
        try:
            resp = requests.delete(f"{C2_URL}/test_sets/{pack}",
                                   headers={'Authorization': auth_content})
        except requests.exceptions.ConnectionError:
            click.echo("Connection refused.")
        else:
            if resp.status_code in {401, 404}:
                click.echo(resp.json()['error'])
            elif resp.status_code != 204:
                click.echo(
                    "Unexpected response from Command and Control Sever.")
예제 #4
0
def delete_executions(password: str, executions: List[int]):
    """Delete the specified EXECUTIONS."""

    key = password.encode()
    for execution in executions:
        signature = signatures.new_signature(key, "DELETE",
                                             f"/executions/{execution}")
        auth_content = signatures.new_authorization_header("Client", signature)
        try:
            resp = requests.delete(f"{C2_URL}/executions/{execution}",
                                   headers={'Authorization': auth_content})
        except requests.exceptions.ConnectionError:
            click.echo("Connection refused.")
        else:
            if resp.status_code in {401, 404}:
                click.echo(resp.json()['error'])
            elif resp.status_code != 204:
                click.echo(
                    "Unexpected response from Command and Control Sever.")
예제 #5
0
def uninstall(password: str, ip: str, port: int, packages: List[str]):
    """Remove the specified PACKAGES from the node at IP:PORT."""

    key = password.encode()
    for pack in packages:
        signature = signatures.new_signature(
            key, "DELETE", f"/environments/{ip}/{port}/installed/{pack}")
        auth_content = signatures.new_authorization_header("Client", signature)
        try:
            resp = requests.delete(
                f"{C2_URL}/environments/{ip}/{port}/installed/{pack}",
                headers={'Authorization': auth_content})
        except requests.exceptions.ConnectionError:
            click.echo("Connection refused.")
        else:
            if resp.status_code in {401, 404, 502, 504}:
                click.echo(resp.json()['error'])
            elif resp.status_code != 204:
                click.echo(
                    "Unexpected response from Command and Control Sever.")
예제 #6
0
def connect_to_c2() -> bool:
    """Tries to make contact with the C&C server.

    Returns
    -------
    bool
        Wheter the C&C was sucessfully reached and it returned a status code
        of 204.
    """

    prepared = rq.Request(
        "POST",
        f"{config['C2_URL']}/environments",
        json={
            'ip': config.get('NAT_IP', config['IP']),
            'port': config.get('NAT_PORT', config['PORT']),
            'platform_info': get_platform_info()
        }).prepare()

    digest = b64encode(sha256(prepared.body).digest()).decode()
    prepared.headers['Digest'] = f"sha-256={digest}"

    headers = ['Digest']
    signature = signatures.new_signature(
        config['C2_SECRET'],
        "POST",
        "/environments",
        signature_headers=headers,
        header_recoverer=lambda h: prepared.headers.get(h))
    prepared.headers['Authorization'] = (
        signatures.new_authorization_header("Node", signature, headers))

    try:
        resp = rq.Session().send(prepared)
    except rq.exceptions.ConnectionError:
        return False

    return resp.status_code == 204
예제 #7
0
def stop_active_environments() -> None:
    """Tries to shutdown all currently active nodes. It also updates the
    database ending all current sessions."""

    get_memory_storage().flushdb(asynchronous=True)

    db = get_database()
    cursor = db.execute("""SELECT env_ip, env_port
        FROM session
        WHERE session_end IS NULL""")

    environments = cursor.fetchall()
    if environments:
        signature = signatures.new_signature(current_app.config['NODE_SECRET'],
                                             "DELETE", "/")
        authorization_content = (signatures.new_authorization_header(
            "C2", signature))

        for env in environments:
            ip = env['env_ip']
            port = env['env_port']
            try:
                resp = rq.delete(
                    f"http://{ip}:{port}/",
                    headers={'Authorization': authorization_content})
            except rq.exceptions.ConnectionError:
                click.echo(f"Node at {ip}:{port} could not be reached.")
            else:
                if resp.status_code != 204:
                    click.echo(
                        f"Unexpected response from node at {ip}:{port}.")
                else:
                    click.echo(f"Node at {ip}:{port} reached.")

        cursor.execute("""UPDATE session
            SET session_end = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
            WHERE session_end IS NULL""")
        db.commit()
예제 #8
0
def delete_installed_package(ip, port, package):
    check_authorization_header(client_key_recoverer)
    check_registered(ip, port)

    signature = signatures.new_signature(current_app.config['NODE_SECRET'],
                                         "DELETE", f"/test_sets/{package}")
    authorization_content = signatures.new_authorization_header(
        "C2", signature)

    environment_key = f"environments:{ip}:{port}"
    memory_storage = get_memory_storage()
    with memory_storage.lock(f"{environment_key}:installed:mutex",
                             timeout=30,
                             sleep=1):
        try:
            resp = rq.delete(f"http://{ip}:{port}/test_sets/{package}",
                             headers={'Authorization': authorization_content})
        except rq.exceptions.ConnectionError:
            abort(504,
                  description="The requested environment could not be reached")

        if resp.status_code == 204:
            installed_cached = memory_storage.hget(environment_key,
                                                   "installed_cached")
            if installed_cached == "1":
                # Updates cache if it exists.
                pipe = memory_storage.pipeline()
                pipe.hdel(environment_key, f"installed:{package}")
                pipe.zrem(f"{environment_key}:installed_index", package)
                pipe.execute()

            return Response(status=204, mimetype="application/json")

        if resp.status_code in {401, 404}:
            return abort(404,
                         description=f"'{package}' not found at {ip}:{port}")

        abort(502, description=f"Unexpected response from node at {ip}:{port}")
예제 #9
0
def upload_compressed_packages(password: str, file_path: click.Path):
    """Uploads a tar.gz file full of packages to the C&C server."""

    if not file_path.endswith(".tar.gz"):
        click.echo("Only .tar.gz extension allowed.")
    else:
        with open(file_path, "rb") as f:
            prepared = requests.Request("PATCH",
                                        f"{C2_URL}/test_sets",
                                        files={
                                            'packages': f
                                        }).prepare()

        digest = b64encode(sha256(prepared.body).digest()).decode()
        prepared.headers['Digest'] = f"sha-256={digest}"

        headers = ['Digest']
        signature = signatures.new_signature(
            password.encode(),
            "PATCH",
            "/test_sets",
            signature_headers=headers,
            header_recoverer=lambda h: prepared.headers.get(h))
        prepared.headers['Authorization'] = (
            signatures.new_authorization_header("Client", signature, headers))

        try:
            resp = requests.Session().send(prepared)
        except requests.exceptions.ConnectionError:
            click.echo("Connection refused.")
        else:
            if resp.status_code in {400, 401, 415}:
                click.echo(resp.json()['error'])
            elif resp.status_code != 204:
                click.echo(
                    "Unexpected response from Command and Control Sever.")
예제 #10
0
def install_packages(ip, port):
    check_digest_header()
    check_authorization_header(client_key_recoverer, "Digest")
    check_registered(ip, port)
    check_is_json()

    packages = request.json
    memory_storage = get_memory_storage()

    with rcl.ReaderLock(memory_storage, "repository", 30, 1):
        with tempfile.SpooledTemporaryFile() as f:
            # Can throw ValueError.
            try:
                test_utils.compress_test_packages(
                    f, packages, current_app.config['TESTS_PATH'])
            except ValueError as e:
                abort(400, description=str(e))
            f.seek(0)
            prepared = rq.Request("PATCH",
                                  f"http://{ip}:{port}/test_sets",
                                  files={
                                      'packages': f
                                  }).prepare()

        digest = b64encode(sha256(prepared.body).digest()).decode()
        prepared.headers['Digest'] = f"sha-256={digest}"

        headers = ['Digest']
        signature = signatures.new_signature(
            current_app.config['NODE_SECRET'],
            "PATCH",
            "/test_sets",
            signature_headers=headers,
            header_recoverer=lambda h: prepared.headers.get(h))
        prepared.headers['Authorization'] = (
            signatures.new_authorization_header("C2", signature, headers))

        environment_key = f"environments:{ip}:{port}"
        with memory_storage.lock(f"{environment_key}:installed:mutex",
                                 timeout=30,
                                 sleep=1):
            try:
                resp = rq.Session().send(prepared)
            except rq.exceptions.ConnectionError:
                abort(
                    504,
                    description="The requested environment could not be reached"
                )
            if resp.status_code == 204:
                installed_cached = memory_storage.hget(environment_key,
                                                       "installed_cached")
                if installed_cached == "1":
                    # Updates cache if it exists.
                    pipe = memory_storage.pipeline()
                    for pack in packages:
                        pipe.hset(environment_key, f"installed:{pack}",
                                  memory_storage.get(f"repository:{pack}"))
                        pipe.zadd(f"{environment_key}:installed_index",
                                  {pack: 0})
                    pipe.execute()

                return Response(status=204, mimetype="application/json")

    if resp.status_code in {400, 401, 415}:
        abort(500,
              description="Something went wrong when handling the request")

    abort(502, description=f"Unexpected response from node at {ip}:{port}")