Skip to content

Commit 188d860

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 188d860

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-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: 10 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,14 @@ def staging_action_builder(config, staging_details)
2224
end
2325

2426
def task_action_builder(config, task)
27+
unprocessable!('Attempting to run task as root user, which is not permitted.') if task.docker? && !docker_run_action_user_permitted?(task.run_action_user)
28+
2529
TaskActionBuilder.new(config, task, { droplet_path: task.droplet.docker_receipt_image })
2630
end
2731

2832
def desired_lrp_builder(config, process)
33+
unprocessable!('Attempting to run process as root user, which is not permitted.') if process.docker? && !docker_run_action_user_permitted?(process.run_action_user)
34+
2935
DesiredLrpBuilder.new(config, builder_opts(process))
3036
end
3137

@@ -46,6 +52,10 @@ def container_env_vars_for_process(process)
4652
additional_env = []
4753
additional_env + WindowsEnvironmentSage.ponder(process.app)
4854
end
55+
56+
def docker_run_action_user_permitted?(run_action_user)
57+
Config.config.get(:allow_docker_root_user) || ROOT_USERS.exclude?(run_action_user)
58+
end
4959
end
5060
end
5161
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 task 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 task 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: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,130 @@ module Docker
118118
end
119119
end
120120
end
121+
122+
context 'when root user is allowed' do
123+
before do
124+
TestConfig.override(allow_docker_root_user: true)
125+
end
126+
127+
context 'and the process does not set a user' do
128+
let(:app) { AppModel.make(:docker, { droplet: }) }
129+
let(:process) { ProcessModel.make(:docker, { app: }) }
130+
131+
context 'and the droplet docker execution metadata sets the root user' do
132+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
133+
let(:droplet) do
134+
DropletModel.make(:docker, {
135+
state: DropletModel::STAGED_STATE,
136+
docker_receipt_image: 'the-image',
137+
execution_metadata: droplet_execution_metadata
138+
})
139+
end
140+
141+
it 'creates a diego DesiredLRPBuilder' do
142+
expect do
143+
lifecycle_protocol.desired_lrp_builder(config, process)
144+
end.not_to raise_error
145+
end
146+
end
147+
148+
context 'and the droplet docker execution metadata sets the 0 user' do
149+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
150+
let(:droplet) do
151+
DropletModel.make(:docker, {
152+
state: DropletModel::STAGED_STATE,
153+
docker_receipt_image: 'the-image',
154+
execution_metadata: droplet_execution_metadata
155+
})
156+
end
157+
158+
it 'creates a diego TaskActionBuilder' do
159+
expect do
160+
lifecycle_protocol.desired_lrp_builder(config, process)
161+
end.not_to raise_error
162+
end
163+
end
164+
165+
context 'and the droplet docker execution metadata does not set a user' do
166+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"]}' }
167+
let(:droplet) do
168+
DropletModel.make(:docker, {
169+
state: DropletModel::STAGED_STATE,
170+
docker_receipt_image: 'the-image',
171+
execution_metadata: droplet_execution_metadata
172+
})
173+
end
174+
175+
it 'creates a diego TaskActionBuilder' do
176+
expect do
177+
lifecycle_protocol.desired_lrp_builder(config, process)
178+
end.not_to raise_error
179+
end
180+
end
181+
end
182+
end
183+
184+
context 'when root user IS NOT allowed' do
185+
before do
186+
TestConfig.override(allow_docker_root_user: false)
187+
end
188+
189+
context 'and the process does not set a user' do
190+
let(:app) { AppModel.make(:docker, { droplet: }) }
191+
let(:process) { ProcessModel.make(:docker, { app: }) }
192+
193+
context 'and the droplet docker execution metadata sets the root user' do
194+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
195+
let(:droplet) do
196+
DropletModel.make(:docker, {
197+
state: DropletModel::STAGED_STATE,
198+
docker_receipt_image: 'the-image',
199+
execution_metadata: droplet_execution_metadata
200+
})
201+
end
202+
203+
it 'raises an error' do
204+
expect do
205+
lifecycle_protocol.desired_lrp_builder(config, process)
206+
end.to raise_error(::CloudController::Errors::ApiError, 'Attempting to run process as root user, which is not permitted')
207+
end
208+
end
209+
210+
context 'and the droplet docker execution metadata sets the 0 user' do
211+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
212+
let(:droplet) do
213+
DropletModel.make(:docker, {
214+
state: DropletModel::STAGED_STATE,
215+
docker_receipt_image: 'the-image',
216+
execution_metadata: droplet_execution_metadata
217+
})
218+
end
219+
220+
it 'raises an error' do
221+
expect do
222+
lifecycle_protocol.desired_lrp_builder(config, process)
223+
end.to raise_error(::CloudController::Errors::ApiError, 'Attempting to run process as root user, which is not permitted')
224+
end
225+
end
226+
227+
context 'and the droplet docker execution metadata does not set a user' do
228+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"]}' }
229+
let(:droplet) do
230+
DropletModel.make(:docker, {
231+
state: DropletModel::STAGED_STATE,
232+
docker_receipt_image: 'the-image',
233+
execution_metadata: droplet_execution_metadata
234+
})
235+
end
236+
237+
it 'raises an error' do
238+
expect do
239+
lifecycle_protocol.desired_lrp_builder(config, process)
240+
end.to raise_error(::CloudController::Errors::ApiError, 'Attempting to run process as root user, which is not permitted')
241+
end
242+
end
243+
end
244+
end
121245
end
122246

123247
describe '#task_action_builder' do
@@ -138,6 +262,131 @@ module Docker
138262
)
139263
lifecycle_protocol.task_action_builder(config, task)
140264
end
265+
266+
context 'when root user is allowed' do
267+
before do
268+
TestConfig.override(allow_docker_root_user: true)
269+
end
270+
271+
context 'and the task does not set a user' do
272+
let(:app) { AppModel.make(:docker, { droplet: }) }
273+
let(:task) { TaskModel.make(:docker, { droplet:, app: }) }
274+
275+
context 'and the droplet docker execution metadata sets the root user' do
276+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
277+
let(:droplet) do
278+
DropletModel.make(:docker, {
279+
state: DropletModel::STAGED_STATE,
280+
docker_receipt_image: 'the-image',
281+
execution_metadata: droplet_execution_metadata
282+
})
283+
end
284+
285+
it 'creates a diego TaskActionBuilder' do
286+
expect do
287+
lifecycle_protocol.task_action_builder(config, task)
288+
end.not_to raise_error
289+
end
290+
end
291+
292+
context 'and the droplet docker execution metadata sets the 0 user' do
293+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
294+
let(:droplet) do
295+
DropletModel.make(:docker, {
296+
state: DropletModel::STAGED_STATE,
297+
docker_receipt_image: 'the-image',
298+
execution_metadata: droplet_execution_metadata
299+
})
300+
end
301+
302+
it 'creates a diego TaskActionBuilder' do
303+
expect do
304+
lifecycle_protocol.task_action_builder(config, task)
305+
end.not_to raise_error
306+
end
307+
end
308+
309+
context 'and the droplet docker execution metadata does not set a user' do
310+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"]}' }
311+
let(:droplet) do
312+
DropletModel.make(:docker, {
313+
state: DropletModel::STAGED_STATE,
314+
docker_receipt_image: 'the-image',
315+
execution_metadata: droplet_execution_metadata
316+
})
317+
end
318+
319+
it 'creates a diego TaskActionBuilder' do
320+
expect do
321+
lifecycle_protocol.task_action_builder(config, task)
322+
end.not_to raise_error
323+
end
324+
end
325+
end
326+
end
327+
328+
context 'when root user IS NOT allowed' do
329+
before do
330+
TestConfig.override(allow_docker_root_user: false)
331+
end
332+
333+
context 'and the task does not set a user' do
334+
let(:app) { AppModel.make(:docker, { droplet: }) }
335+
let(:task) { TaskModel.make(:docker, { droplet:, app: }) }
336+
337+
context 'and the droplet docker execution metadata sets the root user' do
338+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"root"}' }
339+
let(:droplet) do
340+
DropletModel.make(:docker, {
341+
state: DropletModel::STAGED_STATE,
342+
docker_receipt_image: 'the-image',
343+
execution_metadata: droplet_execution_metadata
344+
})
345+
end
346+
347+
it 'raises an error' do
348+
expect do
349+
lifecycle_protocol.task_action_builder(config, task)
350+
end.to raise_error(::CloudController::Errors::ApiError, 'Attempting to run task as root user, which is not permitted')
351+
end
352+
end
353+
354+
context 'and the droplet docker execution metadata sets the 0 user' do
355+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"],"user":"0"}' }
356+
let(:droplet) do
357+
DropletModel.make(:docker, {
358+
state: DropletModel::STAGED_STATE,
359+
docker_receipt_image: 'the-image',
360+
execution_metadata: droplet_execution_metadata
361+
})
362+
end
363+
364+
it 'raises an error' do
365+
expect do
366+
lifecycle_protocol.desired_lrp_builder(config, task)
367+
end.to raise_error(::CloudController::Errors::ApiError, 'Attempting to run task as root user, which is not permitted')
368+
end
369+
end
370+
371+
context 'and the droplet docker execution metadata does not set a user' do
372+
let(:droplet_execution_metadata) { '{"entrypoint":["/image-entrypoint.sh"]}' }
373+
let(:droplet) do
374+
DropletModel.make(:docker, {
375+
state: DropletModel::STAGED_STATE,
376+
docker_receipt_image: 'the-image',
377+
execution_metadata: droplet_execution_metadata
378+
})
379+
end
380+
381+
it 'raises an error' do
382+
expect do
383+
lifecycle_protocol.desired_lrp_builder(config, process)
384+
end.to raise_error(::CloudController::Errors::ApiError, 'Attempting to run task as root user, which is not permitted')
385+
end
386+
end
387+
end
388+
end
389+
141390
end
142391
end
143392
end

0 commit comments

Comments
 (0)