diff --git a/CONCEPT.md b/CONCEPT.md new file mode 100644 index 0000000..77a8307 --- /dev/null +++ b/CONCEPT.md @@ -0,0 +1,162 @@ +# ctf01d training platform + +Платформа для тренировок команд CTF в режиме attack-defence заточенная под журейную систему ctf01d. + + +Основные функции: +- Каталог сервисов с чекерами +- Список команд +- Автоматическое развертывание игры AD на базе ctf01d в каком либо виртуальном пространстве. + +Система должна будет работать в двух режимах: + +1. Это как каталог сервисов - публично развернутый архив +2. Это как локальная система для тренировок (которая сможет стянуть сервисы из публичного каталога) + +## Технологический стек + +- Сервер: golang +- База данных: postgre +- Клинет: фронт на vanila + jquery + +## Авторизация и пользователи (общее) + +Каждый пользователь имеет такие поля: + +- uuid пользователя +- Имя для входа (login) - *должно быть ограничение на символы, англ + цифры + _, так как он будет использвоаться в url, Уникальное и не изменяемое* +- Видимое имя - *То как будет отображается пользовательдля всех, например: Иван Петров* +- Пароль для входа (password) - *есснно в базе храниться хеш* +- Аватар - *Ссылка на аватарку* +- Рейтинг - *Пока просто каеие то абстрактные очки* + +Cписок ролей пользователей (надо бы что бы была возможность создавать новые роли при необходимости - место для костылей и прочего): + +- user - пользователь, роль по-умолчанию +- moderator - пользователь, который может апрувать часть данных +- admin - администратор + +профиль пользователя: + +- История по командам, в каких играл и в какой сейчас состоит + +### Функции + +- Авторизация - *простая по логин/пароль* +- Регистрация - *Закрытая, пользователь вводит свои креты, и только администратор в ручном режиме будет активировать пользователей* +- Создать пользователя - *Администратор может создавать пользователей самостоятельно* +- Удалить пользователя - *Администратор может удалить пользователя навсегда* +- Сброс пароля - *Администратор может установить пароль для любого пользователя* +- Изменить пароль - *Пользователь может изменить свой пароль при использовании предыдущего* +- Обновить аватар - *Пользователь может загрузить свою новую аватарку* +- Удалить аватар - *Пользователь может удалить свою аватарку, либо админ может сбросить у любого пользователя* + +*пароль должен именть не менее 6 символов* + +## Каталог сервисов + +Каждый сервис имеет такие поля: + +- uuid сервиса +- Уникальное имя +- Публичное описание - *То есть то которое будут видеть все так что там должна быть только общая информация, например: аналог Инстаграм или чат* +- Приватное описание - *Описание уязвимостей, на какие портах работает, какие технологии применялись* +- Автор - *что бы знать в кого писать и для соблюдения авторских прав* +- Копирайт - *для соблюдения авторских прав* +- Аватарка - *для того чтобы интересенее отображалось в списке* +- Архив с сервисом - *Непосредственно то что будет разворачиваться в уязвимомо образе* (должно быть версионирование файлов на стороне сервера) +- Валидный ли архив с сервисом - *Поле обозначает что прошел ли сервис проверки* +- Архив с чекером - *То что будет использоваться журейной системой* (должно быть версионирование файлов на стороне сервера) +- Валидный ли архив с чекером - *Поле обозначает что прошел ли чекер проверки* +- Публичный ли сервис - *то есть доступный ли он для пользователей* +- Дата создания сервиса - *Указывается пользователем* +- Дата последнего изменения полей - *Автоматичеки со стороны сервера* +- Райтап(ы) - ? +- Валидация - *Прошел ли сервис валидацию* +- Рейтинг сервиса + +На текущий момент добавлять/изменять/удалять сервисы может только пользователи с ролью админ. + + +### Функции + +- Добавить сервис - *администратор* +- Изменить сервис - *администратор* +- Установить аватарку - *администратор* +- Удалить аватарку - *администратор* +- Залить архив сервиса - *формально убедиться что в архиве все файлы есть какие надо* +- Залить архив чекера - *формально убедиться что в архиве есть все файлы какие надо* + +### Фичи + +- Если админов много то возможно нужно реализовать защиту от перезаписи данных путем: клиент отправляет данные как было и как стало и сервер уже проверяет что если поля соответсвуют то обновляет, если же изменилось какое либо поле то отправить ответ какое поле изменилось клиенту. +- При загрузки zip для сервиса необходимо проверить корректно ли работает чекер (проверить все коды и состояния) + +## Команды + +Поля: + +- uuid команды +- Имя команды +- Аватарка +- Университет +- Описание +- Ссылка на оффициальный сайт +- Текущий капитан + +Отдельной таблицей по отношению к пользователям (многие ко многим? - да владеть двумя командами это возможно): +- команда uuid - *уникальный идентификатор* +- Роль в команде - (четыре захаркоженные роли: owner, captain, vice-captain, player) +- Название роли в команде - *роли придуманные капитанами и замами команд* +- публичный комментарий - *виден всем* +- приватный коментарий - *виден только владельцу капитану и зам капитана* + +Отдельной таблицей: +- Запрос на создание команды (имя, университет, описание) - *Принимает заявку администратор* + +Также нужна таблица историческая, по изменению состава команды + изменению ролей в команде: + +Пишем туда но ничего не удаляем. + +### Функции + +- Запрос создания команды - *От пользователя* +- Подтверждение создания команды - *Администратор* +- Создание команды - *Администратор* +- Удаление команды - *Администратор* +- Редактирование команды - *Администратор либо владелец* +- Добавление участников в команду *Администратор* +- Приглашение в команду - *Владелец команды, капитан или зам капитана* +- Запрос в состав команды - *Пользователь* +- Установка капитана/зам капитана/Участника - *Владелец команды и администратор* +- Установка зам капитана/Участника - *Капитан команды и администратор* +- Установка "Название роли в команде"/"публичный комментарий"/"приватный коментарий" для учатников команды - *Владелец команды и администратор* + + +### Логика + +- Владельцев команды может быть несколько. +- Капитан команды может быть только один. +- Зам капитанов команды может быть несколько (потом можно будет сделать ограничение). +- Участников команды может быть много. +- Владеть можно несколькими командами +- Капитаном можно быть только в одной команде. +- Участвовать можно в нескольких камандах одновременно (Но во время игры надо будет проверять на уникальность списка учатников). + + +## Игры + +Поля: +- Название +- Дата начала +- Дата окончания +- Кто проводит + + +### TODO + +- Создание игр по расписанию (какие сервисы какие команды и когда) +- Список кто с кем и когда +- Когда игру открывают - то опенвпн (или какой то там будет доступ) для соответсвующих команд и пользователей. +- Где находиться scoreboard +- По завершению игры - надо сохранять результат скоребоарда. diff --git a/html/assets/css/index.css b/html/assets/css/index.css index ca3e84f..96e294a 100644 --- a/html/assets/css/index.css +++ b/html/assets/css/index.css @@ -29,4 +29,9 @@ body,html{ .btn-menu-top-left { margin-left: 10px; +} + +.services-card { + display: inline-block; + margin: 10px; } \ No newline at end of file diff --git a/html/assets/js/api.js b/html/assets/js/api.js index 0b4a1fa..a134cba 100644 --- a/html/assets/js/api.js +++ b/html/assets/js/api.js @@ -41,3 +41,42 @@ window.ctf01d_tp_api.auth_session = function (auth_data) { data: JSON.stringify(auth_data), }); } + +window.ctf01d_tp_api.services_list = function() { + return $.ajax({ + url: '/api/services', + method: 'GET', + }); +} + +window.ctf01d_tp_api.service_create = function(service_data) { + return $.ajax({ + url: '/api/services', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(service_data), + }); +} + +window.ctf01d_tp_api.service_update = function(service_id, service_data) { + return $.ajax({ + url: '/api/services/' + service_id, + method: 'PUT', + contentType: 'application/json', + data: JSON.stringify(service_data), + }); +} + +window.ctf01d_tp_api.service_delete = function(service_id) { + return $.ajax({ + url: '/api/services/' + service_id, + method: 'DELETE', + }); +} + +window.ctf01d_tp_api.service_info = function(service_id) { + return $.ajax({ + url: '/api/services/' + service_id, + method: 'GET', + }); +} diff --git a/html/assets/js/index.js b/html/assets/js/index.js index 89ef4cd..a495136 100644 --- a/html/assets/js/index.js +++ b/html/assets/js/index.js @@ -154,7 +154,7 @@ function doSignin() { }) } -function renderGamePage() { +function renderGamesPage() { $('.spa-web-page').css({"display": ""}) $('#games_page').css({"display": "block"}) if (window.location.pathname != "/games/") { @@ -187,6 +187,162 @@ function renderGamePage() { }) } +function serviceCreateOrUpdate() { + $('#services_page_error').css({"display": "none"}); + $('#services_page_error').html(""); + + var service_id = $('#service_update_service_id').val(); + + var serviceName = $('#service_create_name').val(); + var serviceAuthor = $('#service_create_author').val(); + var serviceLogoUrl = $('#service_create_logo_url').val(); + var serviceDescription = $('#service_create_description').val(); + var serviceIsPublic = $('#service_create_is_public').prop('checked') + if (service_id == 0) { + window.ctf01d_tp_api.service_create({ + name: serviceName, + author: serviceAuthor, + logo_url: serviceLogoUrl, + description: serviceDescription, + is_public: serviceIsPublic, + }).fail(function(res) { + $('#services_page_error').css({ + "display": "block" + }); + $('#services_page_error').html("Error creating service"); + console.error(res); + }).done(function(res) { + console.log(res) + $('#modal_edit_or_create_service').modal('hide'); + showSuccessNotification('Service created successfully!') + renderServicesPage(); + }) + } else { + window.ctf01d_tp_api.service_update(service_id, { + name: serviceName, + author: serviceAuthor, + logo_url: serviceLogoUrl, + description: serviceDescription, + is_public: serviceIsPublic, + }).fail(function(res) { + $('#services_page_error').css({ + "display": "block" + }); + $('#services_page_error').html("Error updating service"); + console.error(res); + }).done(function(res) { + console.log(res) + $('#modal_edit_or_create_service').modal('hide'); + showSuccessNotification('Service updated successfully!') + renderServicesPage(); + }) + } +} + +function deleteService(service_id) { + $('#services_page_error').css({"display": "none"}); + $('#services_page_error').html(""); + window.ctf01d_tp_api.service_delete(service_id).fail(function(res) { + $('#services_page_error').css({ + "display": "block" + }); + $('#services_page_error').html("Error delete service"); + console.error("services_list", res); + }).done(function(res) { + showSuccessNotification('Service deleted successfully!') + renderServicesPage(); + }) +} + +function showCreateService() { + $('#services_page_error').css({"display": "none"}); + $('#services_page_error').html(""); + + $('#service_update_service_id').val(0); + $('#service_create_name').val(""); + $('#service_create_author').val(""); + $('#service_create_logo_url').val(""); + $('#service_create_description').val(""); + $('#service_create_is_public').prop('checked', false); + $('#btn_service_create_or_update').html("Create"); + $('#title_service_create_or_update').html("New Service"); + $('#modal_edit_or_create_service').modal('show'); +} + +function showUpdateService(service_id) { + $('#services_page_error').css({"display": "none"}); + $('#services_page_error').html(""); + $('#btn_service_create_or_update').html("Update"); + + window.ctf01d_tp_api.service_info(service_id).fail(function(res) { + $('#services_page_error').css({ + "display": "block" + }); + $('#services_page_error').html("Error get service info"); + }).done(function(res) { + console.log("showUpdateService", res) + $('#service_update_service_id').val(res["id"]) + $('#title_service_create_or_update').html("Update Service #" + res["id"]); + $('#service_create_name').val(res["name"]); + $('#service_create_author').val(res["name"]); + $('#service_create_logo_url').val(res["logo_url"]); + $('#service_create_description').val(res["description"]); + if (res["is_public"]) { + $('#service_create_is_public').prop('checked', true); + } else { + $('#service_create_is_public').prop('checked', false); + } + $('#modal_edit_or_create_service').modal('show'); + }) +} + +function renderServicesPage() { + $('.spa-web-page').css({"display": ""}) + $('#services_page').css({"display": "block"}) + $('#services_page_error').css({"display": "none"}); + $('#services_page_error').html(""); + if (window.location.pathname != "/services/") { + window.location.href = "/services/"; + } + window.ctf01d_tp_api.services_list().fail(function(res) { + $('#services_page_error').css({ + "display": "block" + }); + $('#services_page_error').html("Error loading services"); + console.error("services_list", res); + }).done(function(res) { + var servicesHtml = "" + for (var i in res) { + var service_info = res[i]; + console.log("service_info", service_info); + servicesHtml += '
'; + servicesHtml += ' Image of service'; + servicesHtml += '
'; + servicesHtml += '
#' + service_info.id + ' ' + escapeHtml(service_info.name) + '
'; // TODO uuid + servicesHtml += '

' + escapeHtml(service_info.description) + ' by ' + escapeHtml(service_info.author) + '

'; + // TODO + // servicesHtml += ' ' + getHumanTimeHasPassed(new Date(game_info.end_time)) + ''; + servicesHtml += '
'; + servicesHtml += ' '; + servicesHtml += '
'; + servicesHtml += ' '; + servicesHtml += ' '; + servicesHtml += '
'; + servicesHtml += '
'; + + // servicesHtml += '
' + new Date(game_info.end_time) + '
'; + // servicesHtml += ''; + // updateGameTeams('service_teams_' + game_info.id, game_info.id) + } + $('#services_page_list').html(servicesHtml); + }) +} + + function renderPage(pathname) { console.log("pathname", pathname) if (pathname == "/") { @@ -195,12 +351,9 @@ function renderPage(pathname) { }) $('#root_page').css({"display": "block"}) } else if (isGamesPage(pathname)) { - renderGamePage(); + renderGamesPage(); } else if (isServicesPage(pathname)) { - $('.spa-web-page').css({ - "display": "" - }) - $('#services_page').css({"display": "block"}) + renderServicesPage(); } else if (isTeamPage(pathname)) { $('#teams_page').css({"display": "block"}) $('.spa-web-page').css({ diff --git a/html/index.html b/html/index.html index 61ec254..0dd3628 100644 --- a/html/index.html +++ b/html/index.html @@ -150,14 +150,66 @@

-
-
- Services -
+

Services

+ +

+ +

+ +

diff --git a/internal/app/handlers/services.go b/internal/app/handlers/services.go index e72914a..40af0c3 100644 --- a/internal/app/handlers/services.go +++ b/internal/app/handlers/services.go @@ -70,5 +70,5 @@ func (h *Handlers) ListServices(w http.ResponseWriter, r *http.Request) { // fixme implement func (h *Handlers) UpdateService(w http.ResponseWriter, r *http.Request, id int) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNotImplemented) }