Skip to content

Commit 5c86d43

Browse files
committed
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 356d7b1 commit 5c86d43

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-0
lines changed

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

spec/unit/lib/cloud_controller/diego/docker/lifecycle_protocol_spec.rb

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,162 @@ module Docker
118118
end
119119
end
120120
end
121+
122+
context 'when root user is allowed' do
123+
let(:app) { AppModel.make(:docker, { droplet: }) }
124+
125+
before do
126+
TestConfig.override(allow_docker_root_user: true, additional_allowed_process_users: %w[root 0])
127+
end
128+
129+
context 'and the process sets the root user' do
130+
let(:process) { ProcessModel.make(:docker, { app: app, user: 'root'}) }
131+
132+
it 'creates a diego DesiredLrpBuilder' do
133+
expect do
134+
lifecycle_protocol.desired_lrp_builder(config, process)
135+
end.not_to raise_error
136+
end
137+
end
138+
139+
context 'and the process does not set a user' do
140+
let(:process) { ProcessModel.make(:docker, { app: }) }
141+
142+
context 'and the droplet docker execution metadata sets the root user' do
143+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
144+
let(:droplet) do
145+
DropletModel.make(:docker, {
146+
state: DropletModel::STAGED_STATE,
147+
docker_receipt_image: 'the-image',
148+
execution_metadata: droplet_execution_metadata
149+
})
150+
end
151+
152+
it 'creates a diego DesiredLRPBuilder' do
153+
expect do
154+
lifecycle_protocol.desired_lrp_builder(config, process)
155+
end.not_to raise_error
156+
end
157+
end
158+
159+
context 'and the droplet docker execution metadata sets the 0 user' do
160+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
161+
let(:droplet) do
162+
DropletModel.make(:docker, {
163+
state: DropletModel::STAGED_STATE,
164+
docker_receipt_image: 'the-image',
165+
execution_metadata: droplet_execution_metadata
166+
})
167+
end
168+
169+
it 'creates a diego TaskActionBuilder' do
170+
expect do
171+
lifecycle_protocol.desired_lrp_builder(config, process)
172+
end.not_to raise_error
173+
end
174+
end
175+
176+
context 'and the droplet docker execution metadata does not set a user' do
177+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"]}' }
178+
let(:droplet) do
179+
DropletModel.make(:docker, {
180+
state: DropletModel::STAGED_STATE,
181+
docker_receipt_image: 'the-image',
182+
execution_metadata: droplet_execution_metadata
183+
})
184+
end
185+
186+
it 'creates a diego TaskActionBuilder' do
187+
expect do
188+
lifecycle_protocol.desired_lrp_builder(config, process)
189+
end.not_to raise_error
190+
end
191+
end
192+
end
193+
end
194+
195+
context 'when root user IS NOT allowed' do
196+
let(:app) { AppModel.make(:docker, { droplet: }) }
197+
198+
before do
199+
TestConfig.override(allow_docker_root_user: false, additional_allowed_process_users: %w[root 0])
200+
end
201+
202+
context 'and the process does not set a user' do
203+
let(:process) { ProcessModel.make(:docker, { app: }) }
204+
205+
context 'and the droplet docker execution metadata sets the root user' do
206+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
207+
let(:droplet) do
208+
DropletModel.make(:docker, {
209+
state: DropletModel::STAGED_STATE,
210+
docker_receipt_image: 'the-image',
211+
execution_metadata: droplet_execution_metadata
212+
})
213+
end
214+
215+
it 'raises an error' do
216+
expect do
217+
lifecycle_protocol.desired_lrp_builder(config, process)
218+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run process as root user, which is not permitted/)
219+
end
220+
end
221+
222+
context 'and the droplet docker execution metadata sets the 0 user' do
223+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
224+
let(:droplet) do
225+
DropletModel.make(:docker, {
226+
state: DropletModel::STAGED_STATE,
227+
docker_receipt_image: 'the-image',
228+
execution_metadata: droplet_execution_metadata
229+
})
230+
end
231+
232+
it 'raises an error' do
233+
expect do
234+
lifecycle_protocol.desired_lrp_builder(config, process)
235+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run process as root user, which is not permitted/)
236+
end
237+
end
238+
239+
context 'and the droplet docker execution metadata does not set a user' do
240+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"]}' }
241+
let(:droplet) do
242+
DropletModel.make(:docker, {
243+
state: DropletModel::STAGED_STATE,
244+
docker_receipt_image: 'the-image',
245+
execution_metadata: droplet_execution_metadata
246+
})
247+
end
248+
249+
it 'raises an error' do
250+
expect do
251+
lifecycle_protocol.desired_lrp_builder(config, process)
252+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run process as root user, which is not permitted/)
253+
end
254+
end
255+
end
256+
257+
context 'and the process sets the root user' do
258+
let(:process) { ProcessModel.make(:docker, { app: app, user: 'root' }) }
259+
260+
it 'raises an error' do
261+
expect do
262+
lifecycle_protocol.desired_lrp_builder(config, process)
263+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run process as root user, which is not permitted/)
264+
end
265+
end
266+
267+
context 'and the process sets the 0 user' do
268+
let(:process) { ProcessModel.make(:docker, { app: app, user: 0 }) }
269+
270+
it 'raises an error' do
271+
expect do
272+
lifecycle_protocol.desired_lrp_builder(config, process)
273+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run process as root user, which is not permitted/)
274+
end
275+
end
276+
end
121277
end
122278

123279
describe '#task_action_builder' do
@@ -138,6 +294,135 @@ module Docker
138294
)
139295
lifecycle_protocol.task_action_builder(config, task)
140296
end
297+
298+
context 'when root user is allowed' do
299+
before do
300+
TestConfig.override(allow_docker_root_user: true, additional_allowed_process_users: %w[root 0])
301+
end
302+
303+
context 'and the task does not set a user' do
304+
let(:app) { AppModel.make(:docker, { droplet: }) }
305+
let(:task) { TaskModel.make(:docker, { droplet:, app: }) }
306+
307+
context 'and the droplet docker execution metadata sets the root user' do
308+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
309+
let(:droplet) do
310+
DropletModel.make(:docker, {
311+
state: DropletModel::STAGED_STATE,
312+
docker_receipt_image: 'the-image',
313+
execution_metadata: droplet_execution_metadata
314+
})
315+
end
316+
317+
it 'creates a diego TaskActionBuilder' do
318+
expect do
319+
lifecycle_protocol.task_action_builder(config, task)
320+
end.not_to raise_error
321+
end
322+
end
323+
324+
context 'and the droplet docker execution metadata sets the 0 user' do
325+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
326+
let(:droplet) do
327+
DropletModel.make(:docker, {
328+
state: DropletModel::STAGED_STATE,
329+
docker_receipt_image: 'the-image',
330+
execution_metadata: droplet_execution_metadata
331+
})
332+
end
333+
334+
it 'creates a diego TaskActionBuilder' do
335+
expect do
336+
lifecycle_protocol.task_action_builder(config, task)
337+
end.not_to raise_error
338+
end
339+
end
340+
341+
context 'and the droplet docker execution metadata does not set a user' do
342+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"]}' }
343+
let(:droplet) do
344+
DropletModel.make(:docker, {
345+
state: DropletModel::STAGED_STATE,
346+
docker_receipt_image: 'the-image',
347+
execution_metadata: droplet_execution_metadata
348+
})
349+
end
350+
351+
it 'creates a diego TaskActionBuilder' do
352+
expect do
353+
lifecycle_protocol.task_action_builder(config, task)
354+
end.not_to raise_error
355+
end
356+
end
357+
end
358+
end
359+
360+
context 'when root user IS NOT allowed' do
361+
before do
362+
TestConfig.override(allow_docker_root_user: false, additional_allowed_process_users: %w[root 0])
363+
end
364+
365+
context 'and the task does not set a user' do
366+
let(:app) { AppModel.make(:docker, { droplet: }) }
367+
let(:task) { TaskModel.make(:docker, { droplet:, app: }) }
368+
369+
context 'and the droplet docker execution metadata sets the root user' do
370+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
371+
let(:droplet) do
372+
DropletModel.make(:docker, {
373+
state: DropletModel::STAGED_STATE,
374+
docker_receipt_image: 'the-image',
375+
execution_metadata: droplet_execution_metadata
376+
})
377+
end
378+
379+
it 'raises an error' do
380+
expect do
381+
lifecycle_protocol.task_action_builder(config, task)
382+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run task as root user, which is not permitted/)
383+
end
384+
end
385+
386+
context 'and the droplet docker execution metadata sets the 0 user' do
387+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
388+
let(:droplet) do
389+
DropletModel.make(:docker, {
390+
state: DropletModel::STAGED_STATE,
391+
docker_receipt_image: 'the-image',
392+
execution_metadata: droplet_execution_metadata
393+
})
394+
end
395+
396+
it 'raises an error' do
397+
expect do
398+
lifecycle_protocol.task_action_builder(config, task)
399+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run task as root user, which is not permitted/)
400+
end
401+
end
402+
end
403+
404+
context 'and the task sets the root user' do
405+
let(:app) { AppModel.make(:docker, { droplet: }) }
406+
let(:task) { TaskModel.make(:docker, { droplet: droplet, app: app, user: 'root' }) }
407+
408+
it 'raises an error' do
409+
expect do
410+
lifecycle_protocol.task_action_builder(config, task)
411+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run task as root user, which is not permitted/)
412+
end
413+
end
414+
415+
context 'and the task sets the 0 user' do
416+
let(:app) { AppModel.make(:docker, { droplet: }) }
417+
let(:task) { TaskModel.make(:docker, { droplet: droplet, app: app, user: '0' }) }
418+
419+
it 'raises an error' do
420+
expect do
421+
lifecycle_protocol.task_action_builder(config, task)
422+
end.to raise_error(::CloudController::Errors::ApiError, /Attempting to run task as root user, which is not permitted/)
423+
end
424+
end
425+
end
141426
end
142427
end
143428
end

0 commit comments

Comments
 (0)