From 15d8984a9e2b584bb9594082d497362cc3633fb6 Mon Sep 17 00:00:00 2001 From: Hauke Klement Date: Tue, 17 Mar 2015 17:14:25 +0100 Subject: [PATCH] added the ability to prohibit network access for code submissions executed using Docker --- app/controllers/execution_environments_controller.rb | 2 +- app/models/execution_environment.rb | 1 + app/views/execution_environments/_form.html.slim | 4 ++++ app/views/execution_environments/show.html.slim | 2 +- config/locales/de.yml | 1 + config/locales/en.yml | 1 + ...8_add_network_enabled_to_execution_environments.rb | 11 +++++++++++ db/schema.rb | 3 ++- lib/docker_client.rb | 1 + spec/factories/execution_environment.rb | 9 +++++++++ spec/lib/docker_client_spec.rb | 4 ++++ spec/models/execution_environment_spec.rb | 6 +++++- 12 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20150317115338_add_network_enabled_to_execution_environments.rb diff --git a/app/controllers/execution_environments_controller.rb b/app/controllers/execution_environments_controller.rb index 2eed11a4..5e69f8fc 100644 --- a/app/controllers/execution_environments_controller.rb +++ b/app/controllers/execution_environments_controller.rb @@ -29,7 +29,7 @@ class ExecutionEnvironmentsController < ApplicationController end def execution_environment_params - params[:execution_environment].permit(:docker_image, :exposed_ports, :editor_mode, :file_extension, :file_type_id, :help, :indent_size, :memory_limit, :name, :permitted_execution_time, :pool_size, :run_command, :test_command, :testing_framework).merge(user_id: current_user.id, user_type: current_user.class.name) + params[:execution_environment].permit(:docker_image, :exposed_ports, :editor_mode, :file_extension, :file_type_id, :help, :indent_size, :memory_limit, :name, :network_enabled, :permitted_execution_time, :pool_size, :run_command, :test_command, :testing_framework).merge(user_id: current_user.id, user_type: current_user.class.name) end private :execution_environment_params diff --git a/app/models/execution_environment.rb b/app/models/execution_environment.rb index dd8ee8b4..e49a869c 100644 --- a/app/models/execution_environment.rb +++ b/app/models/execution_environment.rb @@ -16,6 +16,7 @@ class ExecutionEnvironment < ActiveRecord::Base validate :working_docker_image?, if: :validate_docker_image? validates :docker_image, presence: true validates :memory_limit, numericality: {greater_than_or_equal_to: DockerClient::MINIMUM_MEMORY_LIMIT, only_integer: true}, presence: true + validates :network_enabled, inclusion: {in: [true, false]} validates :name, presence: true validates :permitted_execution_time, numericality: {only_integer: true}, presence: true validates :pool_size, numericality: {only_integer: true}, presence: true diff --git a/app/views/execution_environments/_form.html.slim b/app/views/execution_environments/_form.html.slim index 67bf216b..e1b02c0a 100644 --- a/app/views/execution_environments/_form.html.slim +++ b/app/views/execution_environments/_form.html.slim @@ -20,6 +20,10 @@ .form-group = f.label(:memory_limit) = f.number_field(:memory_limit, class: 'form-control', min: DockerClient::MINIMUM_MEMORY_LIMIT, value: f.object.memory_limit || DockerClient::DEFAULT_MEMORY_LIMIT) + .checkbox + label + = f.check_box(:network_enabled) + = t('activerecord.attributes.execution_environment.network_enabled') .form-group = f.label(:permitted_execution_time) = f.number_field(:permitted_execution_time, class: 'form-control', min: 1) diff --git a/app/views/execution_environments/show.html.slim b/app/views/execution_environments/show.html.slim index b0cc38b2..da9fd9c8 100644 --- a/app/views/execution_environments/show.html.slim +++ b/app/views/execution_environments/show.html.slim @@ -5,7 +5,7 @@ h1 = row(label: 'execution_environment.name', value: @execution_environment.name) = row(label: 'execution_environment.user', value: link_to(@execution_environment.author, @execution_environment.author)) = row(label: 'execution_environment.file_type', value: @execution_environment.file_type.present? ? link_to(@execution_environment.file_type, @execution_environment.file_type) : nil) -- [:docker_image, :exposed_ports, :memory_limit, :permitted_execution_time, :pool_size, :run_command, :test_command].each do |attribute| +- [:docker_image, :exposed_ports, :memory_limit, :network_enabled, :permitted_execution_time, :pool_size, :run_command, :test_command].each do |attribute| = row(label: "execution_environment.#{attribute}", value: @execution_environment.send(attribute)) = row(label: 'execution_environment.testing_framework', value: @testing_framework_adapter.try(:framework_name)) = row(label: 'execution_environment.help', value: render_markdown(@execution_environment.help)) diff --git a/config/locales/de.yml b/config/locales/de.yml index 800a1497..76d8e3d9 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -14,6 +14,7 @@ de: file_type_id: Standard-Dateityp help: Hilfetext memory_limit: Speicher-Limit (in MB) + network_enabled: Netzwerkzugriff name: Name permitted_execution_time: Erlaubte Ausführungszeit (in Sekunden) pool_size: Docker-Container-Pool-Größe diff --git a/config/locales/en.yml b/config/locales/en.yml index 33e101f2..978b342a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -15,6 +15,7 @@ en: help: Help Text memory_limit: Memory Limit (in MB) name: Name + network_enabled: Network Enabled permitted_execution_time: Permitted Execution Time (in Seconds) pool_size: Docker Container Pool Size run_command: Run Command diff --git a/db/migrate/20150317115338_add_network_enabled_to_execution_environments.rb b/db/migrate/20150317115338_add_network_enabled_to_execution_environments.rb new file mode 100644 index 00000000..2d86bdaf --- /dev/null +++ b/db/migrate/20150317115338_add_network_enabled_to_execution_environments.rb @@ -0,0 +1,11 @@ +class AddNetworkEnabledToExecutionEnvironments < ActiveRecord::Migration + def change + add_column :execution_environments, :network_enabled, :boolean + + reversible do |direction| + direction.up do + ExecutionEnvironment.update_all(network_enabled: true) + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 3cfd596c..2f2bdcb9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150317083739) do +ActiveRecord::Schema.define(version: 20150317115338) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -47,6 +47,7 @@ ActiveRecord::Schema.define(version: 20150317083739) do t.integer "pool_size" t.integer "file_type_id" t.integer "memory_limit" + t.boolean "network_enabled" end create_table "exercises", force: true do |t| diff --git a/lib/docker_client.rb b/lib/docker_client.rb index f47dad26..a7a77e6c 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -29,6 +29,7 @@ class DockerClient { 'Image' => find_image_by_tag(execution_environment.docker_image).info['RepoTags'].first, 'Memory' => execution_environment.memory_limit.megabytes, + 'NetworkDisabled' => !execution_environment.network_enabled?, 'OpenStdin' => true, 'StdinOnce' => true } diff --git a/spec/factories/execution_environment.rb b/spec/factories/execution_environment.rb index 1e1b10fb..a58e879a 100644 --- a/spec/factories/execution_environment.rb +++ b/spec/factories/execution_environment.rb @@ -6,6 +6,7 @@ FactoryGirl.define do association :file_type, factory: :dot_coffee help name 'CoffeeScript' + network_enabled false permitted_execution_time 10.seconds pool_size 0 run_command 'coffee' @@ -19,6 +20,7 @@ FactoryGirl.define do association :file_type, factory: :dot_html help name 'HTML5' + network_enabled false permitted_execution_time 10.seconds pool_size 0 run_command 'touch' @@ -34,6 +36,7 @@ FactoryGirl.define do association :file_type, factory: :dot_java help name 'Java 8' + network_enabled false permitted_execution_time 10.seconds pool_size 0 run_command 'make run' @@ -49,6 +52,7 @@ FactoryGirl.define do association :file_type, factory: :dot_rb help name 'JRuby 1.7' + network_enabled false permitted_execution_time 10.seconds pool_size 0 run_command 'jruby %{filename}' @@ -64,6 +68,7 @@ FactoryGirl.define do association :file_type, factory: :dot_js help name 'Node.js' + network_enabled false permitted_execution_time 10.seconds pool_size 0 run_command 'node %{filename}' @@ -77,6 +82,7 @@ FactoryGirl.define do association :file_type, factory: :dot_py help name 'Python 3.4' + network_enabled false permitted_execution_time 10.seconds pool_size 0 run_command 'python3 %{filename}' @@ -92,6 +98,7 @@ FactoryGirl.define do association :file_type, factory: :dot_rb help name 'Ruby 2.2' + network_enabled false permitted_execution_time 10.seconds pool_size 0 run_command 'ruby %{filename}' @@ -108,6 +115,7 @@ FactoryGirl.define do exposed_ports '4567' help name 'Sinatra' + network_enabled true permitted_execution_time 15.minutes pool_size 0 run_command 'ruby %{filename}' @@ -123,6 +131,7 @@ FactoryGirl.define do association :file_type, factory: :dot_sql help name 'SQLite' + network_enabled false permitted_execution_time 1.minute pool_size 0 run_command 'sqlite3 /database.db -init %{filename} -html' diff --git a/spec/lib/docker_client_spec.rb b/spec/lib/docker_client_spec.rb index 287891c7..84dc518f 100644 --- a/spec/lib/docker_client_spec.rb +++ b/spec/lib/docker_client_spec.rb @@ -36,6 +36,10 @@ describe DockerClient, docker: true do expect(container_creation_options).to include('Memory' => execution_environment.memory_limit.megabytes) end + it 'specifies whether network access is enabled' do + expect(container_creation_options).to include('NetworkDisabled' => !execution_environment.network_enabled?) + end + it 'specifies to open the standard input stream once' do expect(container_creation_options).to include('OpenStdin' => true, 'StdinOnce' => true) end diff --git a/spec/models/execution_environment_spec.rb b/spec/models/execution_environment_spec.rb index a5ad2ae8..f5a207bf 100644 --- a/spec/models/execution_environment_spec.rb +++ b/spec/models/execution_environment_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe ExecutionEnvironment do - let(:execution_environment) { described_class.create } + let(:execution_environment) { described_class.create.tap { |execution_environment| execution_environment.update(network_enabled: nil) } } it 'validates that the Docker image works', docker: true do expect(execution_environment).to receive(:validate_docker_image?).and_return(true) @@ -32,6 +32,10 @@ describe ExecutionEnvironment do expect(execution_environment.errors[:name]).to be_present end + it 'validates the presence of the network enabled flag' do + expect(execution_environment.errors[:network_enabled]).to be_present + end + it 'validates the numericality of the permitted run time' do execution_environment.update(permitted_execution_time: Math::PI) expect(execution_environment.errors[:permitted_execution_time]).to be_present