Skip to content

Commit 6d1abbe

Browse files
authored
Optionally disallow the use of 'root' user for Processes and Tasks of docker lifecycle Apps (#4452)
* Add 'allow_docker_root_user' config property * Remove duplicate docker? check in helper * Disallow Process and Task 'root' user at runtime * Ignore user in droplet docker execution metadata via dockerfile for staging because user may be subsequently overridden on Process or Task model * Enforce that 'root' or '0' user is not used at runtime by task_action and desired_lrp builders * Re-raise the error in desire app handler. If not re-raised, error is supressed with no clear user feedback.
1 parent 275e080 commit 6d1abbe

File tree

10 files changed

+318
-2
lines changed

10 files changed

+318
-2
lines changed

app/models/runtime/process_model.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,8 +576,6 @@ def permitted_users
576576
end
577577

578578
def docker_run_action_user
579-
return AppModel::DEFAULT_CONTAINER_USER unless docker?
580-
581579
desired_droplet&.docker_user.presence || AppModel::DEFAULT_DOCKER_CONTAINER_USER
582580
end
583581

config/cloud_controller.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ max_retained_deployments_per_app: 100
6868
max_retained_builds_per_app: 100
6969
max_retained_revisions_per_app: 100
7070
additional_allowed_process_users: ['ContainerUser', 'TestUser']
71+
allow_docker_root_user: true
7172

7273
broker_client_default_async_poll_interval_seconds: 60
7374
broker_client_max_async_poll_duration_minutes: 10080

lib/cloud_controller/config_schemas/api_schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class ApiSchema < VCAP::Config
4545
default_health_check_timeout: Integer,
4646
maximum_health_check_timeout: Integer,
4747
additional_allowed_process_users: Array,
48+
allow_docker_root_user: bool,
4849

4950
instance_file_descriptor_limit: Integer,
5051

lib/cloud_controller/config_schemas/clock_schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ class ClockSchema < VCAP::Config
188188
max_retained_builds_per_app: Integer,
189189
max_retained_revisions_per_app: Integer,
190190
additional_allowed_process_users: Array,
191+
allow_docker_root_user: bool,
191192

192193
diego_sync: { frequency_in_seconds: Integer },
193194

lib/cloud_controller/config_schemas/deployment_updater_schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class DeploymentUpdaterSchema < VCAP::Config
5858
maximum_app_disk_in_mb: Integer,
5959
instance_file_descriptor_limit: Integer,
6060
additional_allowed_process_users: Array,
61+
allow_docker_root_user: bool,
6162

6263
deployment_updater: {
6364
update_frequency_in_seconds: Integer,

lib/cloud_controller/config_schemas/worker_schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class WorkerSchema < VCAP::Config
198198
default_app_log_rate_limit_in_bytes_per_second: Integer,
199199
default_app_ssh_access: bool,
200200
additional_allowed_process_users: Array,
201+
allow_docker_root_user: bool,
201202

202203
jobs: {
203204
global: {

lib/cloud_controller/diego/desire_app_handler.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ def create_or_update_app(process, client)
1212
if e.name == 'RunnerError' && e.message['the requested resource already exists']
1313
existing_lrp = client.get_app(process)
1414
client.update_app(process, existing_lrp)
15+
elsif e.name == 'UnprocessableEntity'
16+
raise
1517
end
1618
end
1719
end

lib/cloud_controller/diego/docker/lifecycle_protocol.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module CloudController
88
module Diego
99
module Docker
1010
class LifecycleProtocol
11+
ROOT_USERS = %w[root 0].freeze
12+
1113
def lifecycle_data(staging_details)
1214
lifecycle_data = Diego::Docker::LifecycleData.new
1315
lifecycle_data.docker_image = staging_details.package.image
@@ -22,10 +24,18 @@ def staging_action_builder(config, staging_details)
2224
end
2325

2426
def task_action_builder(config, task)
27+
if task.docker? && !docker_run_action_user_permitted?(task.run_action_user)
28+
raise ::CloudController::Errors::ApiError.new_from_details('UnprocessableEntity', 'Attempting to run task as root user, which is not permitted.')
29+
end
30+
2531
TaskActionBuilder.new(config, task, { droplet_path: task.droplet.docker_receipt_image })
2632
end
2733

2834
def desired_lrp_builder(config, process)
35+
if process.docker? && !docker_run_action_user_permitted?(process.run_action_user)
36+
raise ::CloudController::Errors::ApiError.new_from_details('UnprocessableEntity', 'Attempting to run process as root user, which is not permitted.')
37+
end
38+
2939
DesiredLrpBuilder.new(config, builder_opts(process))
3040
end
3141

@@ -46,6 +56,10 @@ def container_env_vars_for_process(process)
4656
additional_env = []
4757
additional_env + WindowsEnvironmentSage.ponder(process.app)
4858
end
59+
60+
def docker_run_action_user_permitted?(run_action_user)
61+
Config.config.get(:allow_docker_root_user) || ROOT_USERS.exclude?(run_action_user)
62+
end
4963
end
5064
end
5165
end

spec/unit/lib/cloud_controller/diego/desire_app_handler_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ module Diego
5151
expect(client).to have_received(:get_app).exactly(2).times
5252
end
5353
end
54+
55+
context 'when root user is not permitted' do
56+
it 'raises a CloudController::Errors::ApiError' do
57+
allow(client).to receive(:desire_app).and_raise(CloudController::Errors::ApiError.new_from_details('UnprocessableEntity',
58+
'Attempting to run process as root user, which is not permitted.'))
59+
expect { DesireAppHandler.create_or_update_app(process, client) }.to(raise_error do |error|
60+
expect(error).to be_a(CloudController::Errors::ApiError)
61+
expect(error.name).to eq('UnprocessableEntity')
62+
expect(error.message).to include('Attempting to run process as root user, which is not permitted')
63+
end)
64+
end
65+
end
5466
end
5567
end
5668
end

0 commit comments

Comments
 (0)