This repository has been archived by the owner on Dec 21, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
448 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
# CTFd-Containers | ||
# CTFd-Docker | ||
Plugin to give CTFd the ability to manage Docker containers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
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 | ||
from .models import Containers | ||
|
||
from . import utils | ||
|
||
def load(app): | ||
app.db.create_all() | ||
admin_containers = Blueprint('admin_containers', __name__, template_folder='templates') | ||
|
||
|
||
@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('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')) | ||
|
||
|
||
@admin_containers.route('/admin/containers/import', methods=['POST']) | ||
@admins_only | ||
def import_container(): | ||
name = request.form.get('name') | ||
if not set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'): | ||
return redirect(url_for('admin_containers.list_container')) | ||
utils.import_image(name=name) | ||
return redirect(url_for('admin_containers.list_container')) | ||
|
||
app.register_blueprint(admin_containers) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{% extends "admin/base.html" %} | ||
|
||
{% block content %} | ||
{% endblock %} | ||
|
||
{% block scripts %} | ||
<script type="text/javascript"> | ||
window.location = script_root + "/admin/containers" | ||
</script> | ||
{% endblock %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from CTFd.models import db | ||
|
||
|
||
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) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
{% 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 class="modal fade" id="import-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">Import Image</h4> | ||
</div> | ||
<form method="POST" action="{{ request.script_root }}/admin/containers/import" 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 image name"> | ||
</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> | ||
<button class="btn btn-theme btn-outlined create-challenge" data-toggle="modal" data-target="#import-container-modal"> | ||
Import Image | ||
</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 %} |
Oops, something went wrong.