diff --git a/ContainerManager/README.md b/ContainerManager/README.md index f4bb440a..80c313b4 100644 --- a/ContainerManager/README.md +++ b/ContainerManager/README.md @@ -40,4 +40,76 @@ Connecting to a Container Daemon: 2. Connect to socket connection running on port 5506 using `nc localhost 5506`. 3. Run the challenge by sending `create_connection:` to the socket connection. This will return the web address of running challenge. +Running Unit Tests using lettuce: +----------------- + +1. Install lettuce with pip by running: pip install lettuce +2. Change directory to ContainerManager +3. If running for the first time run install.py to download, install, build and configure the hackademic docker image. +4. Start the container-daemon.py in the background (./container-daemon.py > /dev/null &) + OR Run it on another terminal (Recommended for debugging) +5. Change directory to ContainerManager/tests +6. Run 'lettuce' (without quotes) +7. let's lettuce it! + +Using the socket API with communicator.py +----------------- + +The provided communicator.py script supports 3 commands: +1. create_container +2. kill_container +3. list_containers + +Example Usage: + ./communicator.py create_container samplechallenge + OUTPUT: + samplechallenge + http://localhost:4776/samplechallenge + + ./communicator.py list_containers + OUTPUT: + [{ + "Status": "Up 15 seconds", + "Created": 1457876140, + "Image": "hackademic:latest", + "Labels": {}, + "NetworkSettings": { + "Networks": { + "bridge": { + "NetworkID": "", + "MacAddress": "02:42:ac:11:00:02", + "GlobalIPv6PrefixLen": 0, + "Links": null, + "GlobalIPv6Address": "", + "IPv6Gateway": "", + "IPAMConfig": null, + "EndpointID": "5db72707f05bf3dfa4f67a9c803e126190293400d7d5f15a84f2e108bd733a4b", + "IPPrefixLen": 16, + "IPAddress": "172.17.0.2", + "Gateway": "172.17.0.1", + "Aliases": null + } + } + }, + "HostConfig": { + "NetworkMode": "default" + }, + "ImageID": "sha256:9e9357439b68e1d2f8230cd87b307560b1587fa2b36b2ce9649d3942e90416a6", + "Command": "/sbin/my_init", + "Names": ["/desperate_hamilton"], + "Id": "12a7b763a846fc05f1bfb41656ea1d045bfabf0788dc4b795bdf4f1e0c3a1329", + "Ports": [{ + "IP": "0.0.0.0", + "Type": "tcp", + "PublicPort": 4776, + "PrivatePort": 80 + }] + }] + + ./communicator.py kill_container 12a7b763a846fc05f1bfb41656ea1d045bfabf0788dc4b795bdf4f1e0c3a1329 + OUTPUT: + 12a7b763a846fc05f1bfb41656ea1d045bfabf0788dc4b795bdf4f1e0c3a1329 + + + A UI has to be added to provide this as an interface to the admin. diff --git a/ContainerManager/communicator.py b/ContainerManager/communicator.py old mode 100644 new mode 100755 index 6599e8b7..85ca41d0 --- a/ContainerManager/communicator.py +++ b/ContainerManager/communicator.py @@ -2,6 +2,7 @@ import json import socket +import sys __author__ = "AnirudhAnand (a0xnirudh) < anirudh@init-labs.org" diff --git a/ContainerManager/container_daemon.py b/ContainerManager/container_daemon.py index 28454541..211c2c83 100644 --- a/ContainerManager/container_daemon.py +++ b/ContainerManager/container_daemon.py @@ -42,6 +42,7 @@ def create_container(self, challenge=None): str(exception)) exit("Check logs for more details") + print "[*] Container Created: " + cli.get("Id") return "http://localhost:" + str(port) + "/" + str(challenge) def generate_port(self): @@ -57,16 +58,41 @@ def list_containers(self): def kill_container(self, containerid): self.client.kill(containerid) + print "[*] Container Killed: " + containerid def auto_container_killer(self): + print "[*] Auto Container Killer Started" + # Store the network activity for all the containers + # Key: Container ID + # Value: Number of recieved packets (can be changed below, see 'rx_packets') + containers_network_activity = {} while True: - sleep(300) + print "[*] Auto Container Killer Sleeping..." + # Poll every 10 minutes + sleep(600) + print "[*] Checking for Containers with no recieved packets since last check..." container_list = self.client.containers() for i in range(0, len(container_list)): - temp = container_list[i].get("Status") - flag = re.findall(self.timer, temp) - if flag: - self.kill_container(container_list[i].get("Id")) + container_id = container_list[i].get("Id") + stats = self.client.stats(container_id, True) + current_network_activity = 0 + + # 'stats' is a generator object + for stat in stats: + # Reading number of recieved packets in the docker container + current_network_activity = stat['networks']['eth0']['rx_packets'] + break + try: + # If previous network activity is same as the current one then kill the container + if current_network_activity <= containers_network_activity[container_id]: + self.kill_container(container_id) + else: + # Update the network activity for the container in the dictionary + containers_network_activity[container_id] = current_network_activity + # If the container is newly created add an entry for it in the dictionary + except KeyError as err: + containers_network_activity[container_id] = current_network_activity + def create_socket(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -78,6 +104,7 @@ def create_socket(self): + msg[1] sys.exit(1) s.listen(10) + print "[*] Docker Daemon started at: " + self.HOST + ":" + str(self.PORT) while True: conn, addr = s.accept() thread.start_new_thread(self.client_thread, (conn,)) diff --git a/ContainerManager/tests/features/steps.py b/ContainerManager/tests/features/steps.py new file mode 100644 index 00000000..56418f6f --- /dev/null +++ b/ContainerManager/tests/features/steps.py @@ -0,0 +1,52 @@ +from lettuce import * +from subprocess import check_output +import json + +communicator_path = '../communicator.py' + +@step("I create a docker container for '(.*)'") +def i_create_a_docker_container_for(step, challenge): + byte_output = check_output([communicator_path, 'create_container', challenge]) + + # Getting the port number for the newly created container + slash_split = byte_output.split('/') + colon_split = slash_split[2].split(':') + port_no = colon_split[1] + world.container_port = int(port_no) + +@step("I see the container listed") +def i_see_the_container_listed(step): + byte_output = check_output([communicator_path, 'list_containers']) + containers = json.loads(byte_output) + + # Getting the Container ID from the port number from previously created container + world.container_id = '' + for container in containers: + if container['Ports'][0]['PublicPort'] == world.container_port: + world.container_id = container['Id'] + break + + assert world.container_id != '', \ + "Found Container %s" % world.container_id + + +@step("I kill the docker container") +def i_kill_the_docker_container(step): + check_output([communicator_path, 'kill_container', world.container_id]) + + +@step("I do not see the container listed") +def i_do_not_see_it_listed(step): + byte_output = check_output([communicator_path, 'list_containers']) + containers = json.loads(byte_output) + + # Checking whether the Container ID still exist in the list + containerFound = False + for container in containers: + if container['Id'] == world.container_id: + containerFound = True + break + assert not containerFound, "Found Container: %s" % str(containerFound) + + + diff --git a/ContainerManager/tests/features/zero.feature b/ContainerManager/tests/features/zero.feature new file mode 100644 index 00000000..37225f5f --- /dev/null +++ b/ContainerManager/tests/features/zero.feature @@ -0,0 +1,14 @@ +Feature: Manipulating Docker Containers + Creating/Killing/Listing a Docker Container using + the provided socket interface and + running 'samplechallenge' + + + Scenario: Create a container with samplechallenge + When I create a docker container for 'samplechallenge' + Then I see the container listed + + + Scenario: Killing the container + When I kill the docker container + Then I do not see the container listed