Ensured testing of TLS usage for each Docker API call. Stopped unintentional KeyError in finding unused ports
parent
44aa8af317
commit
30219cd549
|
@ -4,9 +4,30 @@ from CTFd.utils.user import get_ip
|
|||
from CTFd.utils.uploads import delete_file
|
||||
from CTFd.plugins import register_plugin_assets_directory, bypass_csrf_protection
|
||||
from CTFd.schemas.tags import TagSchema
|
||||
from CTFd.models import db, ma, Challenges, Teams, Users, Solves, Fails, Flags, Files, Hints, Tags, ChallengeFiles
|
||||
from CTFd.utils.decorators import admins_only, authed_only, during_ctf_time_only, require_verified_emails
|
||||
from CTFd.utils.decorators.visibility import check_challenge_visibility, check_score_visibility
|
||||
from CTFd.models import (
|
||||
db,
|
||||
ma,
|
||||
Challenges,
|
||||
Teams,
|
||||
Users,
|
||||
Solves,
|
||||
Fails,
|
||||
Flags,
|
||||
Files,
|
||||
Hints,
|
||||
Tags,
|
||||
ChallengeFiles,
|
||||
)
|
||||
from CTFd.utils.decorators import (
|
||||
admins_only,
|
||||
authed_only,
|
||||
during_ctf_time_only,
|
||||
require_verified_emails,
|
||||
)
|
||||
from CTFd.utils.decorators.visibility import (
|
||||
check_challenge_visibility,
|
||||
check_score_visibility,
|
||||
)
|
||||
from CTFd.utils.user import get_current_team
|
||||
from CTFd.utils.user import get_current_user
|
||||
from CTFd.utils.user import is_admin, authed
|
||||
|
@ -16,9 +37,25 @@ from CTFd.api.v1.scoreboard import ScoreboardDetail
|
|||
import CTFd.utils.scores
|
||||
from CTFd.api.v1.challenges import ChallengeList, Challenge
|
||||
from flask_restplus import Namespace, Resource
|
||||
from flask import request, Blueprint, jsonify, abort, render_template, url_for, redirect, session
|
||||
from flask import (
|
||||
request,
|
||||
Blueprint,
|
||||
jsonify,
|
||||
abort,
|
||||
render_template,
|
||||
url_for,
|
||||
redirect,
|
||||
session,
|
||||
)
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import TextField, SubmitField, BooleanField, HiddenField, FileField, SelectMultipleField
|
||||
from wtforms import (
|
||||
TextField,
|
||||
SubmitField,
|
||||
BooleanField,
|
||||
HiddenField,
|
||||
FileField,
|
||||
SelectMultipleField,
|
||||
)
|
||||
from wtforms.validators import DataRequired, ValidationError
|
||||
from werkzeug.utils import secure_filename
|
||||
import requests
|
||||
|
@ -30,10 +67,12 @@ import hashlib
|
|||
import random
|
||||
from CTFd.plugins import register_admin_plugin_menu_bar
|
||||
|
||||
|
||||
class DockerConfig(db.Model):
|
||||
"""
|
||||
Docker Config Model. This model stores the config for docker API connections.
|
||||
"""
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
hostname = db.Column("hostname", db.String(64), index=True)
|
||||
tls_enabled = db.Column("tls_enabled", db.Boolean, index=True)
|
||||
|
@ -42,10 +81,12 @@ class DockerConfig(db.Model):
|
|||
client_key = db.Column("client_key", db.String, index=True)
|
||||
repositories = db.Column("repositories", db.String, index=True)
|
||||
|
||||
|
||||
class DockerChallengeTracker(db.Model):
|
||||
"""
|
||||
Docker Container Tracker. This model stores the users/teams active docker containers.
|
||||
"""
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
team_id = db.Column("team_id", db.String, index=True)
|
||||
user_id = db.Column("user_id", db.String, index=True)
|
||||
|
@ -53,24 +94,37 @@ class DockerChallengeTracker(db.Model):
|
|||
timestamp = db.Column("timestamp", db.Integer, index=True)
|
||||
revert_time = db.Column("revert_time", db.Integer, index=True)
|
||||
instance_id = db.Column("instance_id", db.String, index=True)
|
||||
ports = db.Column('ports', db.String, index=True)
|
||||
host = db.Column('host', db.String, index=True)
|
||||
ports = db.Column("ports", db.String, index=True)
|
||||
host = db.Column("host", db.String, index=True)
|
||||
|
||||
|
||||
class DockerConfigForm(FlaskForm):
|
||||
"""
|
||||
Docker Config Form. This Form Handles the Docker Config data.
|
||||
"""
|
||||
|
||||
id = HiddenField()
|
||||
hostname = TextField('Docker Hostname', render_kw={"placeholder": "10.10.10.10:2376", "autofocus" : "true"}, validators=[DataRequired("Hostname name is required")])
|
||||
tls_enabled = BooleanField('TLS Enabled?')
|
||||
ca_cert = FileField('CA Cert')
|
||||
client_cert = FileField('Client Cert')
|
||||
client_key = FileField('Client Key')
|
||||
repositories = SelectMultipleField('Repositories')
|
||||
submit = SubmitField('Submit')
|
||||
hostname = TextField(
|
||||
"Docker Hostname",
|
||||
render_kw={"placeholder": "10.10.10.10:2376", "autofocus": "true"},
|
||||
validators=[DataRequired("Hostname name is required")],
|
||||
)
|
||||
tls_enabled = BooleanField("TLS Enabled?")
|
||||
ca_cert = FileField("CA Cert")
|
||||
client_cert = FileField("Client Cert")
|
||||
client_key = FileField("Client Key")
|
||||
repositories = SelectMultipleField("Repositories")
|
||||
submit = SubmitField("Submit")
|
||||
|
||||
|
||||
def define_docker_admin(app):
|
||||
admin_docker_config = Blueprint('admin_docker_config', __name__, template_folder='templates', static_folder='assets')
|
||||
admin_docker_config = Blueprint(
|
||||
"admin_docker_config",
|
||||
__name__,
|
||||
template_folder="templates",
|
||||
static_folder="assets",
|
||||
)
|
||||
|
||||
@admin_docker_config.route("/admin/docker_config", methods=["GET", "POST"])
|
||||
@admins_only
|
||||
def docker_config():
|
||||
|
@ -81,22 +135,31 @@ def define_docker_admin(app):
|
|||
b = docker
|
||||
else:
|
||||
b = DockerConfig()
|
||||
try: ca_cert = request.files['ca_cert'].stream.read()
|
||||
except: ca_cert = ''
|
||||
try: client_cert = request.files['client_cert'].stream.read()
|
||||
except: client_cert = ''
|
||||
try: client_key = request.files['client_key'].stream.read()
|
||||
except: client_key = ''
|
||||
if len(ca_cert) != 0: b.ca_cert = ca_cert
|
||||
if len(client_cert) != 0: b.client_cert = client_cert
|
||||
if len(client_key) != 0: b.client_key = client_key
|
||||
try:
|
||||
ca_cert = request.files["ca_cert"].stream.read()
|
||||
except:
|
||||
ca_cert = ""
|
||||
try:
|
||||
client_cert = request.files["client_cert"].stream.read()
|
||||
except:
|
||||
client_cert = ""
|
||||
try:
|
||||
client_key = request.files["client_key"].stream.read()
|
||||
except:
|
||||
client_key = ""
|
||||
if len(ca_cert) != 0:
|
||||
b.ca_cert = ca_cert
|
||||
if len(client_cert) != 0:
|
||||
b.client_cert = client_cert
|
||||
if len(client_key) != 0:
|
||||
b.client_key = client_key
|
||||
b.hostname = form.hostname.data
|
||||
b.tls_enabled = form.tls_enabled.data
|
||||
if not b.tls_enabled:
|
||||
b.ca_cert = None
|
||||
b.client_cert = None
|
||||
b.client_key = None
|
||||
b.repositories = ','.join(form.repositories.data) or None
|
||||
b.repositories = ",".join(form.repositories.data) or None
|
||||
db.session.add(b)
|
||||
db.session.commit()
|
||||
docker = DockerConfig.query.filter_by(id=1).first()
|
||||
|
@ -110,15 +173,24 @@ def define_docker_admin(app):
|
|||
form.repositories.choices = [(d, d) for d in repos]
|
||||
dconfig = DockerConfig.query.first()
|
||||
try:
|
||||
selected_repos = dconfig.repositories.split(',')
|
||||
selected_repos = dconfig.repositories.split(",")
|
||||
except:
|
||||
selected_repos = []
|
||||
return render_template("docker_config.html", config=dconfig, form=form, repos=selected_repos)
|
||||
return render_template(
|
||||
"docker_config.html", config=dconfig, form=form, repos=selected_repos
|
||||
)
|
||||
|
||||
app.register_blueprint(admin_docker_config)
|
||||
|
||||
|
||||
def define_docker_status(app):
|
||||
admin_docker_status = Blueprint('admin_docker_status', __name__, template_folder='templates', static_folder='assets')
|
||||
admin_docker_status = Blueprint(
|
||||
"admin_docker_status",
|
||||
__name__,
|
||||
template_folder="templates",
|
||||
static_folder="assets",
|
||||
)
|
||||
|
||||
@admin_docker_status.route("/admin/docker_status", methods=["GET", "POST"])
|
||||
@admins_only
|
||||
def docker_admin():
|
||||
|
@ -132,25 +204,32 @@ def define_docker_status(app):
|
|||
name = Users.query.filter_by(id=i.user_id).first()
|
||||
i.user_id = name.name
|
||||
return render_template("admin_docker_status.html", dockers=docker_tracker)
|
||||
|
||||
app.register_blueprint(admin_docker_status)
|
||||
|
||||
|
||||
kill_container = Namespace("nuke", description='Endpoint to nuke containers')
|
||||
@kill_container.route("", methods=['POST','GET'])
|
||||
kill_container = Namespace("nuke", description="Endpoint to nuke containers")
|
||||
|
||||
|
||||
@kill_container.route("", methods=["POST", "GET"])
|
||||
class KillContainerAPI(Resource):
|
||||
@admins_only
|
||||
def get(self):
|
||||
container = request.args.get('container')
|
||||
full = request.args.get('all')
|
||||
container = request.args.get("container")
|
||||
full = request.args.get("all")
|
||||
docker_config = DockerConfig.query.filter_by(id=1).first()
|
||||
docker_tracker = DockerChallengeTracker.query.all()
|
||||
if full == "true":
|
||||
for c in docker_tracker:
|
||||
delete_container(docker_config, c.instance_id)
|
||||
DockerChallengeTracker.query.filter_by(instance_id=c.instance_id).delete()
|
||||
DockerChallengeTracker.query.filter_by(
|
||||
instance_id=c.instance_id
|
||||
).delete()
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
elif container != 'null' and container in [c.instance_id for c in docker_tracker]:
|
||||
elif container != "null" and container in [
|
||||
c.instance_id for c in docker_tracker
|
||||
]:
|
||||
delete_container(docker_config, container)
|
||||
DockerChallengeTracker.query.filter_by(instance_id=container).delete()
|
||||
db.session.commit()
|
||||
|
@ -159,13 +238,14 @@ class KillContainerAPI(Resource):
|
|||
return False
|
||||
return True
|
||||
|
||||
|
||||
# For the Docker Config Page. Gets the Current Repositories available on the Docker Server.
|
||||
def get_repositories(docker, tags=False, repos=False):
|
||||
tls = docker.tls_enabled
|
||||
if not tls:
|
||||
prefix = 'http'
|
||||
prefix = "http"
|
||||
else:
|
||||
prefix = 'https'
|
||||
prefix = "https"
|
||||
try:
|
||||
ca = docker.ca_cert
|
||||
client = docker.client_cert
|
||||
|
@ -183,10 +263,14 @@ def get_repositories(docker, tags=False, repos=False):
|
|||
except:
|
||||
return []
|
||||
host = docker.hostname
|
||||
URL_TEMPLATE = '%s://%s' % (prefix, host)
|
||||
URL_TEMPLATE = "%s://%s" % (prefix, host)
|
||||
if tls:
|
||||
try:
|
||||
r = requests.get(url="%s/images/json?all=1" % URL_TEMPLATE, cert=CERT, verify=ca_file.name)
|
||||
r = requests.get(
|
||||
url="%s/images/json?all=1" % URL_TEMPLATE,
|
||||
cert=CERT,
|
||||
verify=ca_file.name,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
else:
|
||||
|
@ -196,23 +280,24 @@ def get_repositories(docker, tags=False, repos=False):
|
|||
return []
|
||||
result = list()
|
||||
for i in r.json():
|
||||
if not i['RepoTags'] == None:
|
||||
if not i['RepoTags'][0].split(':')[0] == '<none>':
|
||||
if not i["RepoTags"] == None:
|
||||
if not i["RepoTags"][0].split(":")[0] == "<none>":
|
||||
if repos:
|
||||
if not i['RepoTags'][0].split(':')[0] in repos:
|
||||
if not i["RepoTags"][0].split(":")[0] in repos:
|
||||
continue
|
||||
if not tags:
|
||||
result.append(i['RepoTags'][0].split(':')[0])
|
||||
result.append(i["RepoTags"][0].split(":")[0])
|
||||
else:
|
||||
result.append(i['RepoTags'][0])
|
||||
result.append(i["RepoTags"][0])
|
||||
return list(set(result))
|
||||
|
||||
|
||||
def get_unavailable_ports(docker):
|
||||
tls = docker.tls_enabled
|
||||
if not tls:
|
||||
prefix = 'http'
|
||||
prefix = "http"
|
||||
else:
|
||||
prefix = 'https'
|
||||
prefix = "https"
|
||||
try:
|
||||
ca = docker.ca_cert
|
||||
client = docker.client_cert
|
||||
|
@ -230,21 +315,39 @@ def get_unavailable_ports(docker):
|
|||
except:
|
||||
return []
|
||||
host = docker.hostname
|
||||
URL_TEMPLATE = '%s://%s' % (prefix, host)
|
||||
r = requests.get(url="%s/containers/json?all=1" % URL_TEMPLATE, cert=CERT, verify=ca_file.name)
|
||||
URL_TEMPLATE = "%s://%s" % (prefix, host)
|
||||
|
||||
if tls:
|
||||
try:
|
||||
r = requests.get(
|
||||
url="%s/containers/json?all=1" % URL_TEMPLATE,
|
||||
cert=CERT,
|
||||
verify=ca_file.name,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
else:
|
||||
try:
|
||||
r = requests.get(url="%s/containers/json?all=1" % URL_TEMPLATE)
|
||||
except:
|
||||
return []
|
||||
|
||||
result = list()
|
||||
|
||||
for i in r.json():
|
||||
if not i['Ports'] == []:
|
||||
for p in i['Ports']:
|
||||
result.append(p['PublicPort'])
|
||||
if not i["Ports"] == []:
|
||||
for p in i["Ports"]:
|
||||
if "PublicPort" in p:
|
||||
result.append(p["PublicPort"])
|
||||
return result
|
||||
|
||||
|
||||
def get_required_ports(docker, image):
|
||||
tls = docker.tls_enabled
|
||||
if not tls:
|
||||
prefix = 'http'
|
||||
prefix = "http"
|
||||
else:
|
||||
prefix = 'https'
|
||||
prefix = "https"
|
||||
try:
|
||||
ca = docker.ca_cert
|
||||
client = docker.client_cert
|
||||
|
@ -262,18 +365,33 @@ def get_required_ports(docker, image):
|
|||
except:
|
||||
return []
|
||||
host = docker.hostname
|
||||
URL_TEMPLATE = '%s://%s' % (prefix, host)
|
||||
r = requests.get(url="%s/images/%s/json?all=1" % (URL_TEMPLATE, image), cert=CERT, verify=ca_file.name)
|
||||
result = r.json()['ContainerConfig']['ExposedPorts'].keys()
|
||||
URL_TEMPLATE = "%s://%s" % (prefix, host)
|
||||
|
||||
if tls:
|
||||
try:
|
||||
r = requests.get(
|
||||
url="%s/images/%s/json?all=1" % (URL_TEMPLATE, image),
|
||||
cert=CERT,
|
||||
verify=ca_file.name,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
else:
|
||||
try:
|
||||
r = requests.get(url="%s/images/%s/json?all=1" % (URL_TEMPLATE, image))
|
||||
except:
|
||||
return []
|
||||
|
||||
result = r.json()["ContainerConfig"]["ExposedPorts"].keys()
|
||||
return result
|
||||
|
||||
|
||||
def create_container(docker, image, team, portbl):
|
||||
tls = docker.tls_enabled
|
||||
if not tls:
|
||||
prefix = 'http'
|
||||
prefix = "http"
|
||||
else:
|
||||
prefix = 'https'
|
||||
prefix = "https"
|
||||
try:
|
||||
ca = docker.ca_cert
|
||||
client = docker.client_cert
|
||||
|
@ -291,16 +409,16 @@ def create_container(docker, image, team, portbl):
|
|||
except:
|
||||
return []
|
||||
host = docker.hostname
|
||||
URL_TEMPLATE = '%s://%s' % (prefix, host)
|
||||
URL_TEMPLATE = "%s://%s" % (prefix, host)
|
||||
needed_ports = get_required_ports(docker, image)
|
||||
team = hashlib.md5(team.encode("utf-8")).hexdigest()[:10]
|
||||
container_name = "%s_%s" % (image.split(':')[1], team)
|
||||
container_name = "%s_%s" % (image.split(":")[1], team)
|
||||
assigned_ports = dict()
|
||||
for i in needed_ports:
|
||||
while True:
|
||||
assigned_port = random.choice(range(30000, 60000))
|
||||
if assigned_port not in portbl:
|
||||
assigned_ports['%s/tcp' % assigned_port] = { }
|
||||
assigned_ports["%s/tcp" % assigned_port] = {}
|
||||
break
|
||||
ports = dict()
|
||||
bindings = dict()
|
||||
|
@ -308,19 +426,66 @@ def create_container(docker, image, team, portbl):
|
|||
for i in needed_ports:
|
||||
ports[i] = {}
|
||||
bindings[i] = [{"HostPort": tmp_ports.pop()}]
|
||||
headers = {'Content-Type': "application/json"}
|
||||
data = json.dumps({"Image": image, "ExposedPorts": ports, "HostConfig" : { "PortBindings" : bindings } })
|
||||
r = requests.post(url="%s/containers/create?name=%s" % (URL_TEMPLATE, container_name), cert=CERT, verify=ca_file.name, data=data, headers=headers)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
data = json.dumps(
|
||||
{
|
||||
"Image": image,
|
||||
"ExposedPorts": ports,
|
||||
"HostConfig": {"PortBindings": bindings},
|
||||
}
|
||||
)
|
||||
|
||||
if tls:
|
||||
try:
|
||||
r = requests.get(
|
||||
url="%s/containers/create?name=%s" % (URL_TEMPLATE, container_name),
|
||||
cert=CERT,
|
||||
verify=ca_file.name,
|
||||
data=data,
|
||||
headers=headers,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
else:
|
||||
try:
|
||||
r = requests.get(
|
||||
url="%s/containers/create?name=%s" % (URL_TEMPLATE, container_name),
|
||||
data=data,
|
||||
headers=headers,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
|
||||
result = r.json()
|
||||
s = requests.post(url="%s/containers/%s/start" % (URL_TEMPLATE, result['Id']), cert=CERT, verify=ca_file.name, headers=headers)
|
||||
|
||||
if tls:
|
||||
try:
|
||||
s = requests.post(
|
||||
url="%s/containers/%s/start" % (URL_TEMPLATE, result["Id"]),
|
||||
cert=CERT,
|
||||
verify=ca_file.name,
|
||||
headers=headers,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
else:
|
||||
try:
|
||||
s = requests.post(
|
||||
url="%s/containers/%s/start" % (URL_TEMPLATE, result["Id"]),
|
||||
headers=headers,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
|
||||
return result, data
|
||||
|
||||
|
||||
def delete_container(docker, instance_id):
|
||||
tls = docker.tls_enabled
|
||||
if not tls:
|
||||
prefix = 'http'
|
||||
prefix = "http"
|
||||
else:
|
||||
prefix = 'https'
|
||||
prefix = "https"
|
||||
try:
|
||||
ca = docker.ca_cert
|
||||
client = docker.client_cert
|
||||
|
@ -338,26 +503,51 @@ def delete_container(docker, instance_id):
|
|||
except:
|
||||
return []
|
||||
host = docker.hostname
|
||||
URL_TEMPLATE = '%s://%s' % (prefix, host)
|
||||
headers = {'Content-Type': "application/json"}
|
||||
r = requests.delete(url="%s/containers/%s?force=true" % (URL_TEMPLATE, instance_id), cert=CERT, verify=ca_file.name, headers=headers)
|
||||
URL_TEMPLATE = "%s://%s" % (prefix, host)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
if tls:
|
||||
try:
|
||||
r = requests.delete(
|
||||
url="%s/containers/%s?force=true" % (URL_TEMPLATE, instance_id),
|
||||
cert=CERT,
|
||||
verify=ca_file.name,
|
||||
headers=headers,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
else:
|
||||
try:
|
||||
r = requests.delete(
|
||||
url="%s/containers/%s?force=true" % (URL_TEMPLATE, instance_id),
|
||||
headers=headers,
|
||||
)
|
||||
except:
|
||||
return []
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class DockerChallengeType(BaseChallenge):
|
||||
id = "docker"
|
||||
name = "docker"
|
||||
templates = {
|
||||
'create': '/plugins/docker_challenges/assets/create.html',
|
||||
'update': '/plugins/docker_challenges/assets/update.html',
|
||||
'view': '/plugins/docker_challenges/assets/view.html',
|
||||
"create": "/plugins/docker_challenges/assets/create.html",
|
||||
"update": "/plugins/docker_challenges/assets/update.html",
|
||||
"view": "/plugins/docker_challenges/assets/view.html",
|
||||
}
|
||||
scripts = {
|
||||
'create': '/plugins/docker_challenges/assets/create.js',
|
||||
'update': '/plugins/docker_challenges/assets/update.js',
|
||||
'view': '/plugins/docker_challenges/assets/view.js',
|
||||
"create": "/plugins/docker_challenges/assets/create.js",
|
||||
"update": "/plugins/docker_challenges/assets/update.js",
|
||||
"view": "/plugins/docker_challenges/assets/view.js",
|
||||
}
|
||||
route = '/plugins/docker_challenges/assets'
|
||||
blueprint = Blueprint('docker_challenges', __name__, template_folder='templates', static_folder='assets')
|
||||
route = "/plugins/docker_challenges/assets"
|
||||
blueprint = Blueprint(
|
||||
"docker_challenges",
|
||||
__name__,
|
||||
template_folder="templates",
|
||||
static_folder="assets",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update(challenge, request):
|
||||
|
@ -408,21 +598,21 @@ class DockerChallengeType(BaseChallenge):
|
|||
"""
|
||||
challenge = DockerChallenge.query.filter_by(id=challenge.id).first()
|
||||
data = {
|
||||
'id': challenge.id,
|
||||
'name': challenge.name,
|
||||
'value': challenge.value,
|
||||
'docker_image': challenge.docker_image,
|
||||
'description': challenge.description,
|
||||
'category': challenge.category,
|
||||
'state': challenge.state,
|
||||
'max_attempts': challenge.max_attempts,
|
||||
'type': challenge.type,
|
||||
'type_data': {
|
||||
'id': DockerChallengeType.id,
|
||||
'name': DockerChallengeType.name,
|
||||
'templates': DockerChallengeType.templates,
|
||||
'scripts': DockerChallengeType.scripts,
|
||||
}
|
||||
"id": challenge.id,
|
||||
"name": challenge.name,
|
||||
"value": challenge.value,
|
||||
"docker_image": challenge.docker_image,
|
||||
"description": challenge.description,
|
||||
"category": challenge.category,
|
||||
"state": challenge.state,
|
||||
"max_attempts": challenge.max_attempts,
|
||||
"type": challenge.type,
|
||||
"type_data": {
|
||||
"id": DockerChallengeType.id,
|
||||
"name": DockerChallengeType.name,
|
||||
"templates": DockerChallengeType.templates,
|
||||
"scripts": DockerChallengeType.scripts,
|
||||
},
|
||||
}
|
||||
return data
|
||||
|
||||
|
@ -474,11 +664,25 @@ class DockerChallengeType(BaseChallenge):
|
|||
docker = DockerConfig.query.filter_by(id=1).first()
|
||||
try:
|
||||
if is_teams_mode():
|
||||
docker_containers = DockerChallengeTracker.query.filter_by(docker_image=challenge.docker_image).filter_by(team_id=team.id).first()
|
||||
docker_containers = (
|
||||
DockerChallengeTracker.query.filter_by(
|
||||
docker_image=challenge.docker_image
|
||||
)
|
||||
.filter_by(team_id=team.id)
|
||||
.first()
|
||||
)
|
||||
else:
|
||||
docker_containers = DockerChallengeTracker.query.filter_by(docker_image=challenge.docker_image).filter_by(user_id=user.id).first()
|
||||
docker_containers = (
|
||||
DockerChallengeTracker.query.filter_by(
|
||||
docker_image=challenge.docker_image
|
||||
)
|
||||
.filter_by(user_id=user.id)
|
||||
.first()
|
||||
)
|
||||
delete_container(docker, docker_containers.instance_id)
|
||||
DockerChallengeTracker.query.filter_by(instance_id=docker_containers.instance_id).delete()
|
||||
DockerChallengeTracker.query.filter_by(
|
||||
instance_id=docker_containers.instance_id
|
||||
).delete()
|
||||
except:
|
||||
pass
|
||||
solve = Solves(
|
||||
|
@ -515,19 +719,25 @@ class DockerChallengeType(BaseChallenge):
|
|||
db.session.commit()
|
||||
db.session.close()
|
||||
|
||||
|
||||
class DockerChallenge(Challenges):
|
||||
__mapper_args__ = {'polymorphic_identity': 'docker'}
|
||||
id = db.Column(None, db.ForeignKey('challenges.id'), primary_key=True)
|
||||
__mapper_args__ = {"polymorphic_identity": "docker"}
|
||||
id = db.Column(None, db.ForeignKey("challenges.id"), primary_key=True)
|
||||
docker_image = db.Column(db.String, index=True)
|
||||
|
||||
|
||||
# API
|
||||
container_namespace = Namespace("container", description='Endpoint to interact with containers')
|
||||
@container_namespace.route("", methods=['POST','GET'])
|
||||
container_namespace = Namespace(
|
||||
"container", description="Endpoint to interact with containers"
|
||||
)
|
||||
|
||||
|
||||
@container_namespace.route("", methods=["POST", "GET"])
|
||||
class ContainerAPI(Resource):
|
||||
@authed_only
|
||||
# I wish this was Post... Issues with API/CSRF and whatnot. Open to a Issue solving this.
|
||||
def get(self):
|
||||
container = request.args.get('name')
|
||||
container = request.args.get("name")
|
||||
if not container:
|
||||
return abort(403)
|
||||
docker = DockerConfig.query.filter_by(id=1).first()
|
||||
|
@ -538,55 +748,86 @@ class ContainerAPI(Resource):
|
|||
session = get_current_team()
|
||||
# First we'll delete all old docker containers (+2 hours)
|
||||
for i in containers:
|
||||
if int(session.id) == int(i.team_id) and (unix_time(datetime.utcnow()) - int(i.timestamp)) >= 7200:
|
||||
if (
|
||||
int(session.id) == int(i.team_id)
|
||||
and (unix_time(datetime.utcnow()) - int(i.timestamp)) >= 7200
|
||||
):
|
||||
delete_container(docker, i.instance_id)
|
||||
DockerChallengeTracker.query.filter_by(instance_id=i.instance_id).delete()
|
||||
DockerChallengeTracker.query.filter_by(
|
||||
instance_id=i.instance_id
|
||||
).delete()
|
||||
db.session.commit()
|
||||
check = DockerChallengeTracker.query.filter_by(team_id=session.id).filter_by(docker_image=container).first()
|
||||
check = (
|
||||
DockerChallengeTracker.query.filter_by(team_id=session.id)
|
||||
.filter_by(docker_image=container)
|
||||
.first()
|
||||
)
|
||||
else:
|
||||
session = get_current_user()
|
||||
for i in containers:
|
||||
if int(session.id) == int(i.user_id) and (unix_time(datetime.utcnow()) - int(i.timestamp)) >= 7200:
|
||||
if (
|
||||
int(session.id) == int(i.user_id)
|
||||
and (unix_time(datetime.utcnow()) - int(i.timestamp)) >= 7200
|
||||
):
|
||||
delete_container(docker, i.instance_id)
|
||||
DockerChallengeTracker.query.filter_by(instance_id=i.instance_id).delete()
|
||||
DockerChallengeTracker.query.filter_by(
|
||||
instance_id=i.instance_id
|
||||
).delete()
|
||||
db.session.commit()
|
||||
check = DockerChallengeTracker.query.filter_by(user_id=session.id).filter_by(docker_image=container).first()
|
||||
check = (
|
||||
DockerChallengeTracker.query.filter_by(user_id=session.id)
|
||||
.filter_by(docker_image=container)
|
||||
.first()
|
||||
)
|
||||
# If this container is already created, we don't need another one.
|
||||
if check != None and not (unix_time(datetime.utcnow()) - int(check.timestamp)) >= 300:
|
||||
if (
|
||||
check != None
|
||||
and not (unix_time(datetime.utcnow()) - int(check.timestamp)) >= 300
|
||||
):
|
||||
return abort(403)
|
||||
# The exception would be if we are reverting a box. So we'll delete it if it exists and has been around for more than 5 minutes.
|
||||
elif check != None:
|
||||
delete_container(docker, check.instance_id)
|
||||
if is_teams_mode():
|
||||
DockerChallengeTracker.query.filter_by(team_id=session.id).filter_by(docker_image=container).delete()
|
||||
DockerChallengeTracker.query.filter_by(team_id=session.id).filter_by(
|
||||
docker_image=container
|
||||
).delete()
|
||||
else:
|
||||
DockerChallengeTracker.query.filter_by(user_id=session.id).filter_by(docker_image=container).delete()
|
||||
DockerChallengeTracker.query.filter_by(user_id=session.id).filter_by(
|
||||
docker_image=container
|
||||
).delete()
|
||||
db.session.commit()
|
||||
portsbl = get_unavailable_ports(docker)
|
||||
create = create_container(docker, container, session.name, portsbl)
|
||||
ports = json.loads(create[1])['HostConfig']['PortBindings'].values()
|
||||
ports = json.loads(create[1])["HostConfig"]["PortBindings"].values()
|
||||
entry = DockerChallengeTracker(
|
||||
team_id=session.id if is_teams_mode() else None,
|
||||
user_id=session.id if not is_teams_mode() else None,
|
||||
docker_image=container,
|
||||
timestamp=unix_time(datetime.utcnow()),
|
||||
revert_time=unix_time(datetime.utcnow()) + 300,
|
||||
instance_id = create[0]['Id'],
|
||||
ports = ','.join([p[0]['HostPort'] for p in ports]),
|
||||
host = str(docker.hostname).split(':')[0]
|
||||
instance_id=create[0]["Id"],
|
||||
ports=",".join([p[0]["HostPort"] for p in ports]),
|
||||
host=str(docker.hostname).split(":")[0],
|
||||
)
|
||||
db.session.add(entry)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return
|
||||
|
||||
active_docker_namespace = Namespace("docker", description='Endpoint to retrieve User Docker Image Status')
|
||||
@active_docker_namespace.route("", methods=['POST','GET'])
|
||||
|
||||
active_docker_namespace = Namespace(
|
||||
"docker", description="Endpoint to retrieve User Docker Image Status"
|
||||
)
|
||||
|
||||
|
||||
@active_docker_namespace.route("", methods=["POST", "GET"])
|
||||
class DockerStatus(Resource):
|
||||
"""
|
||||
The Purpose of this API is to retrieve a public JSON string of all docker containers
|
||||
in use by the current team/user.
|
||||
"""
|
||||
|
||||
@authed_only
|
||||
def get(self):
|
||||
docker = DockerConfig.query.filter_by(id=1).first()
|
||||
|
@ -598,29 +839,32 @@ class DockerStatus(Resource):
|
|||
tracker = DockerChallengeTracker.query.filter_by(user_id=session.id)
|
||||
data = list()
|
||||
for i in tracker:
|
||||
data.append({
|
||||
'id' : i.id,
|
||||
'team_id' : i.team_id,
|
||||
'user_id' : i.user_id,
|
||||
'docker_image' : i.docker_image,
|
||||
'timestamp' : i.timestamp,
|
||||
'revert_time' : i.revert_time,
|
||||
'instance_id' : i.instance_id,
|
||||
'ports' : i.ports.split(','),
|
||||
'host' : str(docker.hostname).split(':')[0]
|
||||
})
|
||||
return {
|
||||
'success' : True,
|
||||
'data' : data
|
||||
data.append(
|
||||
{
|
||||
"id": i.id,
|
||||
"team_id": i.team_id,
|
||||
"user_id": i.user_id,
|
||||
"docker_image": i.docker_image,
|
||||
"timestamp": i.timestamp,
|
||||
"revert_time": i.revert_time,
|
||||
"instance_id": i.instance_id,
|
||||
"ports": i.ports.split(","),
|
||||
"host": str(docker.hostname).split(":")[0],
|
||||
}
|
||||
)
|
||||
return {"success": True, "data": data}
|
||||
|
||||
docker_namespace = Namespace("docker", description='Endpoint to retrieve dockerstuff')
|
||||
@docker_namespace.route("", methods=['POST','GET'])
|
||||
|
||||
docker_namespace = Namespace("docker", description="Endpoint to retrieve dockerstuff")
|
||||
|
||||
|
||||
@docker_namespace.route("", methods=["POST", "GET"])
|
||||
class DockerAPI(Resource):
|
||||
"""
|
||||
This is for creating Docker Challenges. The purpose of this API is to populate the Docker Image Select form
|
||||
object in the Challenge Creation Screen.
|
||||
"""
|
||||
|
||||
@admins_only
|
||||
def get(self):
|
||||
docker = DockerConfig.query.filter_by(id=1).first()
|
||||
|
@ -628,28 +872,22 @@ class DockerAPI(Resource):
|
|||
if images:
|
||||
data = list()
|
||||
for i in images:
|
||||
data.append({'name':i})
|
||||
return {
|
||||
'success' : True,
|
||||
'data' : data
|
||||
}
|
||||
data.append({"name": i})
|
||||
return {"success": True, "data": data}
|
||||
else:
|
||||
return {
|
||||
'success' : False,
|
||||
'data' : [
|
||||
{
|
||||
'name':'Error in Docker Config!'
|
||||
}
|
||||
]
|
||||
}, 400
|
||||
return (
|
||||
{"success": False, "data": [{"name": "Error in Docker Config!"}]},
|
||||
400,
|
||||
)
|
||||
|
||||
|
||||
def load(app):
|
||||
app.db.create_all()
|
||||
CHALLENGE_CLASSES['docker'] = DockerChallengeType
|
||||
register_plugin_assets_directory(app, base_path='/plugins/docker_challenges/assets')
|
||||
CHALLENGE_CLASSES["docker"] = DockerChallengeType
|
||||
register_plugin_assets_directory(app, base_path="/plugins/docker_challenges/assets")
|
||||
define_docker_admin(app)
|
||||
define_docker_status(app)
|
||||
CTFd_API_v1.add_namespace(docker_namespace, '/docker')
|
||||
CTFd_API_v1.add_namespace(container_namespace, '/container')
|
||||
CTFd_API_v1.add_namespace(active_docker_namespace, '/docker_status')
|
||||
CTFd_API_v1.add_namespace(kill_container, '/nuke')
|
||||
CTFd_API_v1.add_namespace(docker_namespace, "/docker")
|
||||
CTFd_API_v1.add_namespace(container_namespace, "/container")
|
||||
CTFd_API_v1.add_namespace(active_docker_namespace, "/docker_status")
|
||||
CTFd_API_v1.add_namespace(kill_container, "/nuke")
|
||||
|
|
Loading…
Reference in New Issue