Exemple #1
0
def delete_network(workout_id):
    try:
        # First delete any routes specific to the workout
        result = compute.routes().list(project=project, filter='name = {}*'.format(workout_id)).execute()
        if 'items' in result:
            for route in result['items']:
                response = compute.routes().delete(project=project, route=route["name"]).execute()
            try:
                compute.zoneOperations().wait(project=project, zone=zone, operation=response["id"]).execute()
            except:
                pass

        # Now it is safe to delete the networks.
        result = compute.networks().list(project=project, filter='name = {}*'.format(workout_id)).execute()
        if 'items' in result:
            for network in result['items']:
                # Networks are not being deleted because the operation occurs too fast.
                response = compute.networks().delete(project=project, network=network["name"]).execute()
                compute.globalOperations().wait(project=project, operation=response["id"]).execute()
                response = compute.globalOperations().get(project=project, operation=response["id"]).execute()
                if 'error' in response:
                    if not long_delete_network(network["name"], response):
                        return False
        return True
    except():
        print("Error in deleting network for %s" % workout_id)
        return False
Exemple #2
0
 def _delete_orphaned_networks():
     """
     This function iterates through all of the networks and deletes any networks which do not have corresponding
     subnetworks. This operation is safe because all active workouts will have a subnetwork.
     :returns: True if the operations was successful. Otherwise, this returns false.
     """
     networks = compute.networks().list(project=project).execute()
     if 'items' in networks:
         for network in networks['items']:
             if 'subnetworks' not in network:
                 cloud_log("cybergym-app",
                           f"Deleting orphaned network {network['name']}",
                           LOG_LEVELS.INFO)
                 try:
                     response = compute.networks().delete(
                         project=project,
                         network=network["name"]).execute()
                 except HttpError:
                     cloud_log("cybergym-app",
                               f"Error deleting network {network['name']}",
                               LOG_LEVELS.ERROR)
                 if not gcp_operation_wait(service=compute,
                                           response=response,
                                           wait_type="global"):
                     cloud_log(
                         "cybergym-app",
                         f"Timeout waiting for network {network['name']} to delete",
                         LOG_LEVELS.WARNING)
                     pass
     else:
         return False
     return True
Exemple #3
0
    def _delete_network(self):
        i = 0
        # Now it is safe to delete the networks.
        success = True
        result = compute.networks().list(
            project=project, filter=f'name = {self.build_id}*').execute()
        if 'items' in result:
            for network in result['items']:
                # Networks are not being deleted because the operation occurs too fast.
                response = compute.networks().delete(
                    project=project, network=network["name"]).execute()
                if not gcp_operation_wait(service=compute,
                                          response=response,
                                          wait_type="global"):
                    cloud_log(
                        "cybergym-app",
                        f"Timeout waiting for network {network['name']} to delete",
                        LOG_LEVELS.WARNING)
                    success = False
                else:
                    # For workouts with multiple networks, avoid setting success to true if at least one
                    # network has an error.
                    if not success:
                        success = True
        else:
            # Return true since the network has already been deleted
            return True

        if success:
            self._process_workout_deletion()
            return True
        else:
            return False
Exemple #4
0
def create_network(networks, build_id):
    """
    Build the network for the given build specification
    :param networks: A specification of networks and subnetwork to build
    :param build_id: The ID of the build. This will be used for referencing the build by name in the future.
    :return:
    """
    for network in networks:
        network_body = {
            "name": "%s-%s" % (build_id, network['name']),
            "autoCreateSubnetworks": False,
            "region": region
        }
        response = compute.networks().insert(project=project,
                                             body=network_body).execute()
        compute.globalOperations().wait(project=project,
                                        operation=response["id"]).execute()
        time.sleep(10)
        for subnet in network['subnets']:
            subnetwork_body = {
                "name":
                "%s" % (subnet['name']),
                "network":
                "projects/%s/global/networks/%s" %
                (project, network_body['name']),
                "ipCidrRange":
                subnet['ip_subnet']
            }
            response = compute.subnetworks().insert(
                project=project, region=region,
                body=subnetwork_body).execute()
            compute.regionOperations().wait(
                project=project, region=region,
                operation=response["id"]).execute()
Exemple #5
0
def long_delete_network(network, response):
    """
    This is necessary because the network does not always delete. The routes take a while to clear from the GCP.
    :param network: The network name to delete
    :param response: The last response from globalOperations.get()
    :return: Boolean on whether the network delete was successful.
    """
    max_tries = 3
    i = 0
    while 'error' in response and i < max_tries:
        response = compute.networks().delete(project=project, network=network).execute()
        compute.globalOperations().wait(project=project, operation=response["id"]).execute()
        response = compute.globalOperations().get(project=project, operation=response["id"]).execute()
        i += 1

    if i < max_tries:
        return True
    else:
        return False
Exemple #6
0
 def _wait_for_deletion(self, wait_type=ArenaWorkoutDeleteType.SERVER):
     """
     For asynchronous deletion, wait until all jobs have completed.
     @param build_id: The id of the build to use in searching for resources
     @type build_id: String
     @param wait_type: designated type of resource
     @type wait_type: String
     @return: Status
     @rtype: Boolean
     """
     i = 0
     all_deleted = False
     while not all_deleted and i < 10:
         if wait_type == ArenaWorkoutDeleteType.SERVER:
             result = compute.instances().list(
                 project=project,
                 zone=zone,
                 filter=f"name = {self.build_id}*").execute()
         elif wait_type == ArenaWorkoutDeleteType.ROUTES:
             result = compute.routes().list(
                 project=project,
                 filter=f"name = {self.build_id}*").execute()
         elif wait_type == ArenaWorkoutDeleteType.FIREWALL_RULES:
             result = compute.firewalls().list(
                 project=project,
                 filter=f"name = {self.build_id}*").execute()
         elif wait_type == ArenaWorkoutDeleteType.NETWORK:
             result = compute.networks().list(
                 project=project,
                 filter=f"name = {self.build_id}*").execute()
         elif wait_type == ArenaWorkoutDeleteType.SUBNETWORK:
             result = compute.subnetworks().list(
                 project=project,
                 region=region,
                 filter=f"name = {self.build_id}*").execute()
         if 'items' not in result:
             all_deleted = True
         else:
             i += 1
             time.sleep(10)
     return all_deleted
def build_workout(workout_id):
    """
    Builds a workout compute environment according to the specification referenced in the datastore with key workout_id
    :param workout_id: The workout_id key in the datastore holding the build specification
    :return: None
    """
    key = ds_client.key('cybergym-workout', workout_id)
    workout = ds_client.get(key)
    # This can sometimes happen when debugging a workout ID and the Datastore record no longer exists.
    if not workout:
        cloud_log(workout_id,
                  f"The datastore record for {workout_id} no longer exists!",
                  LOG_LEVELS.ERROR)
        raise LookupError

    if 'state' not in workout or not workout['state']:
        state_transition(entity=workout, new_state=BUILD_STATES.START)

    # Create the networks and subnets
    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_NETWORKS):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_NETWORKS)
        for network in workout['networks']:
            cloud_log(workout_id,
                      f"Building network {workout_id}-{network['name']}",
                      LOG_LEVELS.INFO)
            network_body = {
                "name": f"{workout_id}-{network['name']}",
                "autoCreateSubnetworks": False,
                "region": region
            }
            try:
                response = compute.networks().insert(
                    project=project, body=network_body).execute()
                compute.globalOperations().wait(
                    project=project, operation=response["id"]).execute()
                time.sleep(10)
            except HttpError as err:
                # If the network already exists, then this may be a rebuild and ignore the error
                if err.resp.status in [409]:
                    pass
            for subnet in network['subnets']:
                cloud_log(
                    workout_id,
                    f"Building the subnetwork {network_body['name']}-{subnet['name']}",
                    LOG_LEVELS.INFO)
                subnetwork_body = {
                    "name":
                    f"{network_body['name']}-{subnet['name']}",
                    "network":
                    "projects/%s/global/networks/%s" %
                    (project, network_body['name']),
                    "ipCidrRange":
                    subnet['ip_subnet']
                }
                try:
                    response = compute.subnetworks().insert(
                        project=project, region=region,
                        body=subnetwork_body).execute()
                    compute.regionOperations().wait(
                        project=project,
                        region=region,
                        operation=response["id"]).execute()
                except HttpError as err:
                    # If the subnetwork already exists, then this may be a rebuild and ignore the error
                    if err.resp.status in [409]:
                        pass
            state_transition(entity=workout,
                             new_state=BUILD_STATES.COMPLETED_NETWORKS)

    # Now create the server configurations
    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_SERVERS):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_SERVERS)
        pubsub_topic = PUBSUB_TOPICS.MANAGE_SERVER
        publisher = pubsub_v1.PublisherClient()
        topic_path = publisher.topic_path(project, pubsub_topic)
        for server in workout['servers']:
            server_name = f"{workout_id}-{server['name']}"
            cloud_log(workout_id,
                      f"Sending pubsub message to build {server_name}",
                      LOG_LEVELS.INFO)
            publisher.publish(topic_path,
                              data=b'Server Build',
                              server_name=server_name,
                              action=SERVER_ACTIONS.BUILD)
        # Also build the student entry server for the workout
        publisher.publish(topic_path,
                          data=b'Server Build',
                          server_name=f"{workout_id}-student-guacamole",
                          action=SERVER_ACTIONS.BUILD)
        state_transition(entity=workout,
                         new_state=BUILD_STATES.COMPLETED_SERVERS)
    # Create all of the network routes and firewall rules
    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_ROUTES):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_ROUTES)
        cloud_log(
            workout_id,
            f"Creating network routes and firewall rules for {workout_id}",
            LOG_LEVELS.INFO)
        if 'routes' in workout and workout['routes']:
            workout_route_setup(workout_id)
    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_FIREWALL):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_FIREWALL)
        firewall_rules = []
        for rule in workout['firewall_rules']:
            firewall_rules.append({
                "name":
                "%s-%s" % (workout_id, rule['name']),
                "network":
                "%s-%s" % (workout_id, rule['network']),
                "targetTags":
                rule['target_tags'],
                "protocol":
                rule['protocol'],
                "ports":
                rule['ports'],
                "sourceRanges":
                rule['source_ranges']
            })
        create_firewall_rules(firewall_rules)
        state_transition(entity=workout,
                         new_state=BUILD_STATES.COMPLETED_FIREWALL)
    cloud_log(
        workout_id,
        f"Finished the build process with a final state: {workout['state']}",
        LOG_LEVELS.INFO)
Exemple #8
0
def build_workout(workout_id):
    """
    Builds a workout compute environment according to the specification referenced in the datastore with key workout_id
    :param workout_id: The workout_id key in the datastore holding the build specification
    :return: None
    """
    key = ds_client.key('cybergym-workout', workout_id)
    workout = ds_client.get(key)
    # This can sometimes happen when debugging a workout ID and the Datastore record no longer exists.
    if not workout:
        print('No workout for %s exists in the data store' % workout_id)
        return

    startup_scripts = None
    # Parse the assessment specification to obtain any startup scripts for the workout.
    if 'state' not in workout or not workout['state']:
        state_transition(entity=workout, new_state=BUILD_STATES.START)

    if workout['assessment']:
        startup_scripts = get_startup_scripts(workout_id=workout_id,
                                              assessment=workout['assessment'])
    # Create the networks and subnets
    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_NETWORKS):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_NETWORKS)
        print('Creating networks')
        for network in workout['networks']:
            network_body = {
                "name": "%s-%s" % (workout_id, network['name']),
                "autoCreateSubnetworks": False,
                "region": region
            }
            response = compute.networks().insert(project=project,
                                                 body=network_body).execute()
            compute.globalOperations().wait(
                project=project, operation=response["id"]).execute()
            time.sleep(10)
            for subnet in network['subnets']:
                subnetwork_body = {
                    "name":
                    "%s-%s" % (network_body['name'], subnet['name']),
                    "network":
                    "projects/%s/global/networks/%s" %
                    (project, network_body['name']),
                    "ipCidrRange":
                    subnet['ip_subnet']
                }
                response = compute.subnetworks().insert(
                    project=project, region=region,
                    body=subnetwork_body).execute()
                compute.regionOperations().wait(
                    project=project, region=region,
                    operation=response["id"]).execute()
                state_transition(entity=workout,
                                 new_state=BUILD_STATES.COMPLETED_NETWORKS)

    # Now create the server configurations
    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_SERVERS):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_SERVERS)
        print('Creating servers')
        for server in workout['servers']:
            server_name = "%s-%s" % (workout_id, server['name'])
            sshkey = server["sshkey"]
            tags = server['tags']
            machine_type = server["machine_type"]
            network_routing = server["network_routing"]
            min_cpu_platform = server[
                "minCpuPlatform"] if "minCpuPlatform" in server else None
            nics = []
            for n in server['nics']:
                nic = {
                    "network": f"{workout_id}-{n['network']}",
                    "internal_IP": n['internal_IP'],
                    "subnet": f"{workout_id}-{n['network']}-{n['subnet']}",
                    "external_NAT": n['external_NAT']
                }
                # Nested VMs are sometimes used for vulnerable servers. This adds those specified IP addresses as
                # aliases to the NIC
                if 'IP_aliases' in n and n['IP_aliases']:
                    alias_ip_ranges = []
                    for ipaddr in n['IP_aliases']:
                        alias_ip_ranges.append({"ipCidrRange": ipaddr})
                    nic['aliasIpRanges'] = alias_ip_ranges
                nics.append(nic)
            # Add the startup script for assessment as metadata if it exists
            meta_data = None
            if startup_scripts and server['name'] in startup_scripts:
                meta_data = startup_scripts[server['name']]

            create_instance_custom_image(compute=compute,
                                         workout=workout_id,
                                         name=server_name,
                                         custom_image=server['image'],
                                         machine_type=machine_type,
                                         networkRouting=network_routing,
                                         networks=nics,
                                         tags=tags,
                                         meta_data=meta_data,
                                         sshkey=sshkey,
                                         minCpuPlatform=min_cpu_platform)

        state_transition(entity=workout,
                         new_state=BUILD_STATES.COMPLETED_SERVERS)
    # Create the student entry guacamole server
    if check_ordered_workout_state(workout,
                                   BUILD_STATES.BUILDING_STUDENT_ENTRY):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_STUDENT_ENTRY)
        if workout['student_entry']:
            network_name = f"{workout_id}-{workout['student_entry']['network']}"
            student_entry_username = workout['student_entry'][
                'username'] if 'username' in workout['student_entry'] else None
            security_mode = workout['student_entry'][
                'security-mode'] if 'security-mode' in workout[
                    'student_entry'] else 'nla'
            guac_connection = [{
                'workout_id': workout_id,
                'entry_type': workout['student_entry']['type'],
                'ip': workout['student_entry']['ip'],
                'username': student_entry_username,
                'password': workout['student_entry']['password'],
                'security-mode': security_mode
            }]
            build_guacamole_server(build=workout,
                                   network=network_name,
                                   guacamole_connections=guac_connection)
            # Get the workout key again or the state transition will overwrite it
            workout = ds_client.get(
                ds_client.key('cybergym-workout', workout_id))
        else:
            state_transition(entity=workout, new_state=BUILD_STATES.BROKEN)
            return
        state_transition(entity=workout,
                         new_state=BUILD_STATES.COMPLETED_STUDENT_ENTRY)
    # Create all of the network routes and firewall rules
    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_ROUTES):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_ROUTES)
        print('Creating network routes and firewall rules')
        if 'routes' in workout and workout['routes']:
            for route in workout['routes']:
                response = compute.instances().get(
                    project=project,
                    zone=zone,
                    instance=f"{workout_id}-{route['next_hop_instance']}")
                r = {
                    "name":
                    "%s-%s" % (workout_id, route['name']),
                    "network":
                    "%s-%s" % (workout_id, route['network']),
                    "destRange":
                    route['dest_range'],
                    "nextHopInstance":
                    "%s-%s" % (workout_id, route['next_hop_instance'])
                }
                create_route(r)

    if check_ordered_workout_state(workout, BUILD_STATES.BUILDING_FIREWALL):
        state_transition(entity=workout,
                         new_state=BUILD_STATES.BUILDING_FIREWALL)
        firewall_rules = []
        for rule in workout['firewall_rules']:
            firewall_rules.append({
                "name":
                "%s-%s" % (workout_id, rule['name']),
                "network":
                "%s-%s" % (workout_id, rule['network']),
                "targetTags":
                rule['target_tags'],
                "protocol":
                rule['protocol'],
                "ports":
                rule['ports'],
                "sourceRanges":
                rule['source_ranges']
            })

        create_firewall_rules(firewall_rules)


# build_workout('isirdhzjqk')