mirror of https://github.com/JohnHammond/CTFd.git
Refactor Containers into a plugin (#348)
* Removing Containers code * Closes #301selenium-screenshot-testing
parent
92b7ca06ca
commit
2e41886591
|
@ -113,7 +113,7 @@ def create_app(config='CTFd.config.Config'):
|
||||||
from CTFd.challenges import challenges
|
from CTFd.challenges import challenges
|
||||||
from CTFd.scoreboard import scoreboard
|
from CTFd.scoreboard import scoreboard
|
||||||
from CTFd.auth import auth
|
from CTFd.auth import auth
|
||||||
from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_containers, admin_keys, admin_teams
|
from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_keys, admin_teams
|
||||||
from CTFd.utils import init_utils, init_errors, init_logs
|
from CTFd.utils import init_utils, init_errors, init_logs
|
||||||
|
|
||||||
init_utils(app)
|
init_utils(app)
|
||||||
|
@ -131,7 +131,6 @@ def create_app(config='CTFd.config.Config'):
|
||||||
app.register_blueprint(admin_teams)
|
app.register_blueprint(admin_teams)
|
||||||
app.register_blueprint(admin_scoreboard)
|
app.register_blueprint(admin_scoreboard)
|
||||||
app.register_blueprint(admin_keys)
|
app.register_blueprint(admin_keys)
|
||||||
app.register_blueprint(admin_containers)
|
|
||||||
app.register_blueprint(admin_pages)
|
app.register_blueprint(admin_pages)
|
||||||
|
|
||||||
from CTFd.plugins import init_plugins
|
from CTFd.plugins import init_plugins
|
||||||
|
|
|
@ -10,7 +10,7 @@ from sqlalchemy.sql import not_
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from CTFd.utils import admins_only, is_admin, cache, export_ctf, import_ctf
|
from CTFd.utils import admins_only, is_admin, cache, export_ctf, import_ctf
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
from CTFd.scoreboard import get_standings
|
from CTFd.scoreboard import get_standings
|
||||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ from CTFd.admin.statistics import admin_statistics
|
||||||
from CTFd.admin.challenges import admin_challenges
|
from CTFd.admin.challenges import admin_challenges
|
||||||
from CTFd.admin.scoreboard import admin_scoreboard
|
from CTFd.admin.scoreboard import admin_scoreboard
|
||||||
from CTFd.admin.pages import admin_pages
|
from CTFd.admin.pages import admin_pages
|
||||||
from CTFd.admin.containers import admin_containers
|
|
||||||
from CTFd.admin.keys import admin_keys
|
from CTFd.admin.keys import admin_keys
|
||||||
from CTFd.admin.teams import admin_teams
|
from CTFd.admin.teams import admin_teams
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
from CTFd.utils import admins_only, is_admin, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, Hints, Unlocks, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, Hints, Unlocks, DatabaseError
|
||||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||||
from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES
|
from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES
|
||||||
|
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
|
||||||
|
|
||||||
from CTFd import utils
|
|
||||||
|
|
||||||
admin_containers = Blueprint('admin_containers', __name__)
|
|
||||||
|
|
||||||
|
|
||||||
@admin_containers.route('/admin/containers', methods=['GET'])
|
|
||||||
@admins_only
|
|
||||||
def list_container():
|
|
||||||
containers = Containers.query.all()
|
|
||||||
for c in containers:
|
|
||||||
c.status = utils.container_status(c.name)
|
|
||||||
c.ports = ', '.join(utils.container_ports(c.name, verbose=True))
|
|
||||||
return render_template('admin/containers.html', containers=containers)
|
|
||||||
|
|
||||||
|
|
||||||
@admin_containers.route('/admin/containers/<int:container_id>/stop', methods=['POST'])
|
|
||||||
@admins_only
|
|
||||||
def stop_container(container_id):
|
|
||||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
|
||||||
if utils.container_stop(container.name):
|
|
||||||
return '1'
|
|
||||||
else:
|
|
||||||
return '0'
|
|
||||||
|
|
||||||
|
|
||||||
@admin_containers.route('/admin/containers/<int:container_id>/start', methods=['POST'])
|
|
||||||
@admins_only
|
|
||||||
def run_container(container_id):
|
|
||||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
|
||||||
if utils.container_status(container.name) == 'missing':
|
|
||||||
if utils.run_image(container.name):
|
|
||||||
return '1'
|
|
||||||
else:
|
|
||||||
return '0'
|
|
||||||
else:
|
|
||||||
if utils.container_start(container.name):
|
|
||||||
return '1'
|
|
||||||
else:
|
|
||||||
return '0'
|
|
||||||
|
|
||||||
|
|
||||||
@admin_containers.route('/admin/containers/<int:container_id>/delete', methods=['POST'])
|
|
||||||
@admins_only
|
|
||||||
def delete_container(container_id):
|
|
||||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
|
||||||
if utils.delete_image(container.name):
|
|
||||||
db.session.delete(container)
|
|
||||||
db.session.commit()
|
|
||||||
db.session.close()
|
|
||||||
return '1'
|
|
||||||
|
|
||||||
|
|
||||||
@admin_containers.route('/admin/containers/new', methods=['POST'])
|
|
||||||
@admins_only
|
|
||||||
def new_container():
|
|
||||||
name = request.form.get('name')
|
|
||||||
if not set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
|
|
||||||
return redirect(url_for('admin_containers.list_container'))
|
|
||||||
buildfile = request.form.get('buildfile')
|
|
||||||
files = request.files.getlist('files[]')
|
|
||||||
utils.create_image(name=name, buildfile=buildfile, files=files)
|
|
||||||
utils.run_image(name)
|
|
||||||
return redirect(url_for('admin_containers.list_container'))
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
from CTFd.utils import admins_only, is_admin, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||||
|
|
||||||
from CTFd import utils
|
from CTFd import utils
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
from CTFd.utils import admins_only, is_admin, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
|
|
||||||
from CTFd import utils
|
from CTFd import utils
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
from CTFd.utils import admins_only, is_admin, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
from CTFd.scoreboard import get_standings
|
from CTFd.scoreboard import get_standings
|
||||||
|
|
||||||
from CTFd import utils
|
from CTFd import utils
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
from CTFd.utils import admins_only, is_admin, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
|
|
||||||
from CTFd import utils
|
from CTFd import utils
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||||
from CTFd.utils import admins_only, is_admin, cache
|
from CTFd.utils import admins_only, is_admin, cache
|
||||||
from CTFd.models import db, Teams, Solves, Awards, Unlocks, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
from CTFd.models import db, Teams, Solves, Awards, Unlocks, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||||
from passlib.hash import bcrypt_sha256
|
from passlib.hash import bcrypt_sha256
|
||||||
from sqlalchemy.sql import not_
|
from sqlalchemy.sql import not_
|
||||||
|
|
||||||
|
|
|
@ -41,19 +41,6 @@ class Pages(db.Model):
|
||||||
return "<Pages route {0}>".format(self.route)
|
return "<Pages route {0}>".format(self.route)
|
||||||
|
|
||||||
|
|
||||||
class Containers(db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
name = db.Column(db.String(80))
|
|
||||||
buildfile = db.Column(db.Text)
|
|
||||||
|
|
||||||
def __init__(self, name, buildfile):
|
|
||||||
self.name = name
|
|
||||||
self.buildfile = buildfile
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Container ID:(0) {1}>".format(self.id, self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class Challenges(db.Model):
|
class Challenges(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(80))
|
name = db.Column(db.String(80))
|
||||||
|
|
|
@ -47,9 +47,6 @@
|
||||||
</li>
|
</li>
|
||||||
<li><a href="{{ request.script_root }}/admin/teams">Teams</a></li>
|
<li><a href="{{ request.script_root }}/admin/teams">Teams</a></li>
|
||||||
<li><a href="{{ request.script_root }}/admin/scoreboard">Scoreboard</a></li>
|
<li><a href="{{ request.script_root }}/admin/scoreboard">Scoreboard</a></li>
|
||||||
{% if can_create_container() %}
|
|
||||||
<li><a href="{{ request.script_root }}/admin/containers">Containers</a></li>
|
|
||||||
{% endif %}
|
|
||||||
<li><a href="{{ request.script_root }}/admin/chals">Challenges</a></li>
|
<li><a href="{{ request.script_root }}/admin/chals">Challenges</a></li>
|
||||||
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
|
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
|
||||||
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
|
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
|
||||||
|
|
|
@ -1,163 +0,0 @@
|
||||||
{% extends "admin/base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="modal fade" id="create-container-modal" tabindex="-1" role="dialog" aria-labelledby="container-modal-label">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
|
||||||
aria-hidden="true">×</span></button>
|
|
||||||
<h4 class="modal-title" id="container-modal-label">Create Container</h4>
|
|
||||||
</div>
|
|
||||||
<form method="POST" action="{{ request.script_root }}/admin/containers/new" enctype="multipart/form-data">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input type="text" class="form-control" name="name" placeholder="Enter container name">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="buildfile-editor" class="control-label">Build File</label>
|
|
||||||
<textarea id="buildfile-editor" class="form-control" name="buildfile" rows="10" placeholder="Enter container build file"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="container-files">Associated Files
|
|
||||||
<i class="fa fa-question-circle gray-text" data-toggle="tooltip" data-placement="right" title="These files are uploaded alongside your buildfile"></i>
|
|
||||||
</label>
|
|
||||||
<input type="file" name="files[]" id="container-files" multiple>
|
|
||||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="submit" class="btn btn-primary">Create</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="confirm" class="modal fade" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h2 class="text-center"><span id="confirm-container-title"></span> Container</h2>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="height:110px">
|
|
||||||
<div class="row-fluid">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<form method="POST">
|
|
||||||
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
|
|
||||||
<div class="small-6 small-centered text-center columns">
|
|
||||||
<p>Are you sure you want to <span id="confirm-container-method"></span> <strong id="confirm-container-name"></strong>?</p>
|
|
||||||
<button type="button" data-dismiss="modal" class="btn btn-theme btn-outlined">No</button>
|
|
||||||
<button type="button" id="confirm-container" class="btn btn-theme btn-outlined">Yes</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<br>
|
|
||||||
<div style="text-align:center">
|
|
||||||
<h1>Containers</h1>
|
|
||||||
<button class="btn btn-theme btn-outlined create-challenge" data-toggle="modal" data-target="#create-container-modal">
|
|
||||||
New Container
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
{% if containers %}
|
|
||||||
<table id="teamsboard">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td class="text-center"><strong>Status</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center"><strong>Name</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center"><strong>Ports</strong>
|
|
||||||
</td>
|
|
||||||
<td class="text-center"><strong>Settings</strong>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for c in containers %}
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">{{ c.status }}</td>
|
|
||||||
<td class="text-center container_item" id="{{ c.id }}">{{ c.name }}</td>
|
|
||||||
<td class="text-center">{{ c.ports }}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<span>
|
|
||||||
{% if c.status != 'running' %}
|
|
||||||
<i class="fa fa-play"></i>
|
|
||||||
{% else %}
|
|
||||||
<i class="fa fa-stop"></i>
|
|
||||||
{% endif %}
|
|
||||||
<i class="fa fa-times"></i>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function load_confirm_modal(title, url, container_name){
|
|
||||||
var modal = $('#confirm')
|
|
||||||
modal.find('#confirm-container-name').text(container_name)
|
|
||||||
modal.find('#confirm-container-title').text(title)
|
|
||||||
modal.find('#confirm-container-method').text(title.toLowerCase())
|
|
||||||
$('#confirm form').attr('action', url);
|
|
||||||
$('#confirm').modal('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#confirm-container').click(function(e){
|
|
||||||
e.preventDefault();
|
|
||||||
var id = $('#confirm input[name="id"]').val()
|
|
||||||
var user_data = $('#confirm form').serializeArray()
|
|
||||||
$.post($('#confirm form').attr('action'), $('#confirm form').serialize(), function(data){
|
|
||||||
var data = $.parseJSON(JSON.stringify(data))
|
|
||||||
if (data == "1"){
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$('.fa-times').click(function(){
|
|
||||||
var elem = $(this).parent().parent().parent().find('.container_item');
|
|
||||||
var container = elem.attr('id');
|
|
||||||
var container_name = elem.text().trim();
|
|
||||||
load_confirm_modal('Delete', '/admin/containers/'+container+'/delete', container_name)
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.fa-play').click(function(){
|
|
||||||
var elem = $(this).parent().parent().parent().find('.container_item');
|
|
||||||
var container = elem.attr('id');
|
|
||||||
var container_name = elem.text().trim();
|
|
||||||
load_confirm_modal('Start', '/admin/containers/'+container+'/start', container_name)
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.fa-stop').click(function(){
|
|
||||||
var elem = $(this).parent().parent().parent().find('.container_item');
|
|
||||||
var container = elem.attr('id');
|
|
||||||
var container_name = elem.text().trim();
|
|
||||||
load_confirm_modal('Stop', '/admin/containers/'+container+'/stop', container_name)
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).ready(function(){
|
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -47,9 +47,6 @@
|
||||||
</li>
|
</li>
|
||||||
<li><a href="{{ request.script_root }}/admin/teams">Teams</a></li>
|
<li><a href="{{ request.script_root }}/admin/teams">Teams</a></li>
|
||||||
<li><a href="{{ request.script_root }}/admin/scoreboard">Scoreboard</a></li>
|
<li><a href="{{ request.script_root }}/admin/scoreboard">Scoreboard</a></li>
|
||||||
{% if can_create_container() %}
|
|
||||||
<li><a href="{{ request.script_root }}/admin/containers">Containers</a></li>
|
|
||||||
{% endif %}
|
|
||||||
<li><a href="{{ request.script_root }}/admin/chals">Challenges</a></li>
|
<li><a href="{{ request.script_root }}/admin/chals">Challenges</a></li>
|
||||||
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
|
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
|
||||||
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
|
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
|
||||||
|
|
149
CTFd/utils.py
149
CTFd/utils.py
|
@ -13,7 +13,6 @@ import shutil
|
||||||
import six
|
import six
|
||||||
import smtplib
|
import smtplib
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
@ -29,7 +28,7 @@ from itsdangerous import TimedSerializer, BadTimeSignature, Signer, BadSignature
|
||||||
from six.moves.urllib.parse import urlparse, urljoin, quote, unquote
|
from six.moves.urllib.parse import urlparse, urljoin, quote, unquote
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Files, Containers, ip2long, long2ip
|
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Files, ip2long, long2ip
|
||||||
|
|
||||||
cache = Cache()
|
cache = Cache()
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
|
@ -105,7 +104,6 @@ def init_utils(app):
|
||||||
app.jinja_env.globals.update(can_send_mail=can_send_mail)
|
app.jinja_env.globals.update(can_send_mail=can_send_mail)
|
||||||
app.jinja_env.globals.update(ctf_name=ctf_name)
|
app.jinja_env.globals.update(ctf_name=ctf_name)
|
||||||
app.jinja_env.globals.update(ctf_theme=ctf_theme)
|
app.jinja_env.globals.update(ctf_theme=ctf_theme)
|
||||||
app.jinja_env.globals.update(can_create_container=can_create_container)
|
|
||||||
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
|
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
|
||||||
app.jinja_env.globals.update(get_config=get_config)
|
app.jinja_env.globals.update(get_config=get_config)
|
||||||
app.jinja_env.globals.update(hide_scores=hide_scores)
|
app.jinja_env.globals.update(hide_scores=hide_scores)
|
||||||
|
@ -557,136 +555,6 @@ def base64decode(s, urldecode=False):
|
||||||
return decoded
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
@cache.memoize()
|
|
||||||
def can_create_container():
|
|
||||||
try:
|
|
||||||
subprocess.check_output(['docker', 'version'])
|
|
||||||
return True
|
|
||||||
except (subprocess.CalledProcessError, OSError):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def is_port_free(port):
|
|
||||||
s = socket.socket()
|
|
||||||
result = s.connect_ex(('127.0.0.1', port))
|
|
||||||
if result == 0:
|
|
||||||
s.close()
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def create_image(name, buildfile, files):
|
|
||||||
if not can_create_container():
|
|
||||||
return False
|
|
||||||
folder = tempfile.mkdtemp(prefix='ctfd')
|
|
||||||
tmpfile = tempfile.NamedTemporaryFile(dir=folder, delete=False)
|
|
||||||
tmpfile.write(buildfile)
|
|
||||||
tmpfile.close()
|
|
||||||
|
|
||||||
for f in files:
|
|
||||||
if f.filename.strip():
|
|
||||||
filename = os.path.basename(f.filename)
|
|
||||||
f.save(os.path.join(folder, filename))
|
|
||||||
# repository name component must match "[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*"
|
|
||||||
# docker build -f tmpfile.name -t name
|
|
||||||
try:
|
|
||||||
cmd = ['docker', 'build', '-f', tmpfile.name, '-t', name, folder]
|
|
||||||
print(cmd)
|
|
||||||
subprocess.call(cmd)
|
|
||||||
container = Containers(name, buildfile)
|
|
||||||
db.session.add(container)
|
|
||||||
db.session.commit()
|
|
||||||
db.session.close()
|
|
||||||
rmdir(folder)
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def delete_image(name):
|
|
||||||
try:
|
|
||||||
subprocess.call(['docker', 'rm', name])
|
|
||||||
subprocess.call(['docker', 'rmi', name])
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def run_image(name):
|
|
||||||
try:
|
|
||||||
info = json.loads(subprocess.check_output(['docker', 'inspect', '--type=image', name]))
|
|
||||||
|
|
||||||
try:
|
|
||||||
ports_asked = info[0]['Config']['ExposedPorts'].keys()
|
|
||||||
ports_asked = [int(re.sub('[A-Za-z/]+', '', port)) for port in ports_asked]
|
|
||||||
except KeyError:
|
|
||||||
ports_asked = []
|
|
||||||
|
|
||||||
cmd = ['docker', 'run', '-d']
|
|
||||||
ports_used = []
|
|
||||||
for port in ports_asked:
|
|
||||||
if is_port_free(port):
|
|
||||||
cmd.append('-p')
|
|
||||||
cmd.append('{}:{}'.format(port, port))
|
|
||||||
else:
|
|
||||||
cmd.append('-p')
|
|
||||||
ports_used.append('{}'.format(port))
|
|
||||||
cmd += ['--name', name, name]
|
|
||||||
print(cmd)
|
|
||||||
subprocess.call(cmd)
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def container_start(name):
|
|
||||||
try:
|
|
||||||
cmd = ['docker', 'start', name]
|
|
||||||
subprocess.call(cmd)
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def container_stop(name):
|
|
||||||
try:
|
|
||||||
cmd = ['docker', 'stop', name]
|
|
||||||
subprocess.call(cmd)
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def container_status(name):
|
|
||||||
try:
|
|
||||||
data = json.loads(subprocess.check_output(['docker', 'inspect', '--type=container', name]))
|
|
||||||
status = data[0]["State"]["Status"]
|
|
||||||
return status
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return 'missing'
|
|
||||||
|
|
||||||
|
|
||||||
def container_ports(name, verbose=False):
|
|
||||||
try:
|
|
||||||
info = json.loads(subprocess.check_output(['docker', 'inspect', '--type=container', name]))
|
|
||||||
if verbose:
|
|
||||||
ports = info[0]["NetworkSettings"]["Ports"]
|
|
||||||
if not ports:
|
|
||||||
return []
|
|
||||||
final = []
|
|
||||||
for port in ports.keys():
|
|
||||||
final.append("".join([ports[port][0]["HostPort"], '->', port]))
|
|
||||||
return final
|
|
||||||
else:
|
|
||||||
ports = info[0]['Config']['ExposedPorts'].keys()
|
|
||||||
if not ports:
|
|
||||||
return []
|
|
||||||
ports = [int(re.sub('[A-Za-z/]+', '', port)) for port in ports]
|
|
||||||
return ports
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def export_ctf(segments=None):
|
def export_ctf(segments=None):
|
||||||
db = dataset.connect(get_config('SQLALCHEMY_DATABASE_URI'))
|
db = dataset.connect(get_config('SQLALCHEMY_DATABASE_URI'))
|
||||||
if segments is None:
|
if segments is None:
|
||||||
|
@ -714,7 +582,6 @@ def export_ctf(segments=None):
|
||||||
'alembic_version',
|
'alembic_version',
|
||||||
'config',
|
'config',
|
||||||
'pages',
|
'pages',
|
||||||
'containers',
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,7 +642,6 @@ def import_ctf(backup, segments=None, erase=False):
|
||||||
'alembic_version',
|
'alembic_version',
|
||||||
'config',
|
'config',
|
||||||
'pages',
|
'pages',
|
||||||
'containers',
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -812,19 +678,6 @@ def import_ctf(backup, segments=None, erase=False):
|
||||||
db.session.add(page)
|
db.session.add(page)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
elif item == 'containers':
|
|
||||||
saved = json.loads(data)
|
|
||||||
for entry in saved['results']:
|
|
||||||
name = entry['name']
|
|
||||||
buildfile = entry['buildfile']
|
|
||||||
container = Containers.query.filter_by(name=name).first()
|
|
||||||
if container:
|
|
||||||
container.buildfile = buildfile
|
|
||||||
else:
|
|
||||||
container = Containers(name, buildfile)
|
|
||||||
db.session.add(container)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
for segment in segments:
|
for segment in segments:
|
||||||
group = groups[segment]
|
group = groups[segment]
|
||||||
for item in group:
|
for item in group:
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
"""Removes containers table in favor of using a plugin to manage Containers
|
||||||
|
|
||||||
|
Revision ID: cbf5620f8e15
|
||||||
|
Revises: 1ec4a28fe0ff
|
||||||
|
Create Date: 2017-08-12 04:11:45.970248
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'cbf5620f8e15'
|
||||||
|
down_revision = '1ec4a28fe0ff'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('containers')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('containers',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=80), nullable=True),
|
||||||
|
sa.Column('buildfile', sa.Text(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
|
@ -44,16 +44,6 @@ def test_admin_scoreboard():
|
||||||
destroy_ctfd(app)
|
destroy_ctfd(app)
|
||||||
|
|
||||||
|
|
||||||
def test_admin_containers():
|
|
||||||
"""Does admin containers return a 200 by default"""
|
|
||||||
app = create_ctfd()
|
|
||||||
with app.app_context():
|
|
||||||
client = login_as_user(app, name="admin", password="password")
|
|
||||||
r = client.get('/admin/containers')
|
|
||||||
assert r.status_code == 200
|
|
||||||
destroy_ctfd(app)
|
|
||||||
|
|
||||||
|
|
||||||
def test_admin_chals():
|
def test_admin_chals():
|
||||||
"""Does admin chals return a 200 by default"""
|
"""Does admin chals return a 200 by default"""
|
||||||
app = create_ctfd()
|
app = create_ctfd()
|
||||||
|
|
Loading…
Reference in New Issue