Skip to content

Commit 62798e9

Browse files
authored
Validate manifest contains valid utf-8 data (#3717)
1 parent 741ba1f commit 62798e9

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

app/controllers/v3/application_controller.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ def bad_request!(message)
3434
raise CloudController::Errors::ApiError.new_from_details('BadRequest', message)
3535
end
3636

37+
def check_utf8_encoding!(yaml_data)
38+
if yaml_data.is_a?(Hash)
39+
yaml_data.each_value do |value|
40+
check_utf8_encoding!(value)
41+
end
42+
elsif yaml_data.is_a?(Array)
43+
yaml_data.each do |value|
44+
check_utf8_encoding!(value)
45+
end
46+
else
47+
return unless yaml_data.is_a?(String) && !yaml_data.force_encoding('UTF-8').valid_encoding?
48+
49+
message_parse_error!('Invalid UTF-8 encoding in YAML data')
50+
end
51+
end
52+
3753
def message_parse_error!(message)
3854
raise CloudController::Errors::ApiError.new_from_details('MessageParseError', message)
3955
end
@@ -95,6 +111,7 @@ def parsed_yaml
95111
yaml = Psych.safe_load(request.body.read, permitted_classes: [], permitted_symbols: [], aliases: allow_yaml_aliases, strict_integer: true)
96112

97113
message_parse_error!('invalid request body') unless yaml.is_a? Hash
114+
check_utf8_encoding!(yaml)
98115
@parsed_yaml = yaml
99116
rescue Psych::BadAlias
100117
bad_request!('Manifest does not support Anchors and Aliases')

spec/request/space_manifests_spec.rb

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,124 @@
218218
)
219219
end
220220

221+
context 'when the manifest contains binary-encoded URL(s) for the buildpack(s)' do
222+
context 'and it contains non-valid data' do
223+
context 'in the buildpacks section' do
224+
let(:app1_model) { VCAP::CloudController::AppModel.make(space:) }
225+
let(:yml_manifest_with_binary_invalid_buildpacks) do
226+
"---
227+
applications:
228+
- name: #{app1_model.name}
229+
buildpacks:
230+
- !!binary |-
231+
aHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkZm91bmRyeS9uZ2lueC1idWlsZHBhY2suZ2l0ww=="
232+
end
233+
234+
it 'returns an appropriate error' do
235+
post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest_with_binary_invalid_buildpacks, yml_headers(user_header)
236+
expect(last_response.status).to eq(400)
237+
parsed_response = MultiJson.load(last_response.body)
238+
expect(parsed_response['errors'].first['detail']).to eq('Request invalid due to parse error: Invalid UTF-8 encoding in YAML data')
239+
end
240+
end
241+
242+
context 'in the buildpack part' do
243+
let(:app1_model) { VCAP::CloudController::AppModel.make(space:) }
244+
let(:yml_manifest_with_binary_invalid_buildpack) do
245+
"---
246+
applications:
247+
- name: #{app1_model.name}
248+
buildpack: !!binary |-
249+
aHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkZm91bmRyeS9uZ2lueC1idWlsZHBhY2suZ2l0ww=="
250+
end
251+
252+
it 'returns an appropriate error' do
253+
post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest_with_binary_invalid_buildpack, yml_headers(user_header)
254+
expect(last_response.status).to eq(400)
255+
parsed_response = MultiJson.load(last_response.body)
256+
expect(parsed_response['errors'].first['detail']).to eq('Request invalid due to parse error: Invalid UTF-8 encoding in YAML data')
257+
end
258+
end
259+
260+
context 'mixed with valid data for the buildpacks' do
261+
let(:app1_model) { VCAP::CloudController::AppModel.make(space:) }
262+
let(:yml_manifest_with_binary_buildpacks) do
263+
"---
264+
applications:
265+
- name: #{app1_model.name}
266+
buildpacks:
267+
- !!binary |-
268+
aHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkZm91bmRyeS9uZ2lueC1idWlsZHBhY2suZ2l0
269+
- !!binary |-
270+
aHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkZm91bmRyeS9uZ2lueC1idWlsZHBhY2suZ2l0ww=="
271+
end
272+
273+
it 'returns an appropriate error' do
274+
post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest_with_binary_buildpacks, yml_headers(user_header)
275+
expect(last_response.status).to eq(400)
276+
parsed_response = MultiJson.load(last_response.body)
277+
expect(parsed_response['errors'].first['detail']).to eq('Request invalid due to parse error: Invalid UTF-8 encoding in YAML data')
278+
end
279+
end
280+
end
281+
282+
context 'and it contains valid data' do
283+
context 'for the buildpacks' do
284+
let(:app1_model) { VCAP::CloudController::AppModel.make(space:) }
285+
let(:yml_manifest_with_binary_valid_buildpacks) do
286+
"---
287+
applications:
288+
- name: #{app1_model.name}
289+
buildpacks:
290+
- !!binary |-
291+
aHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkZm91bmRyeS9uZ2lueC1idWlsZHBhY2suZ2l0
292+
- !!binary |-
293+
aHR0cHM6Ly9naXRodWIuY29tL2J1aWxkcGFja3MvbXktc3BlY2lhbC1idWlsZHBhY2s="
294+
end
295+
296+
it 'applies the manifest' do
297+
post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest_with_binary_valid_buildpacks, yml_headers(user_header)
298+
expect(last_response.status).to eq(202)
299+
job_guid = VCAP::CloudController::PollableJobModel.last.guid
300+
expect(last_response.headers['Location']).to match(%r{/v3/jobs/#{job_guid}})
301+
302+
Delayed::Worker.new.work_off
303+
expect(VCAP::CloudController::PollableJobModel.find(guid: job_guid)).to be_complete, VCAP::CloudController::PollableJobModel.find(guid: job_guid).cf_api_error
304+
305+
app1_model.reload
306+
lifecycle_data = app1_model.lifecycle_data
307+
expect(lifecycle_data.buildpacks.first).to include('https://github.com/cloudfoundry/nginx-buildpack.git')
308+
expect(lifecycle_data.buildpacks.second).to include('https://github.com/buildpacks/my-special-buildpack')
309+
end
310+
end
311+
312+
context 'for single buildpack' do
313+
let(:app1_model) { VCAP::CloudController::AppModel.make(space:) }
314+
let(:yml_manifest_with_binary_valid_buildpack) do
315+
"---
316+
applications:
317+
- name: #{app1_model.name}
318+
buildpack: !!binary |-
319+
aHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkZm91bmRyeS9uZ2lueC1idWlsZHBhY2suZ2l0"
320+
end
321+
322+
it 'applies the manifest' do
323+
post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest_with_binary_valid_buildpack, yml_headers(user_header)
324+
expect(last_response.status).to eq(202)
325+
job_guid = VCAP::CloudController::PollableJobModel.last.guid
326+
expect(last_response.headers['Location']).to match(%r{/v3/jobs/#{job_guid}})
327+
328+
Delayed::Worker.new.work_off
329+
expect(VCAP::CloudController::PollableJobModel.find(guid: job_guid)).to be_complete, VCAP::CloudController::PollableJobModel.find(guid: job_guid).cf_api_error
330+
331+
app1_model.reload
332+
lifecycle_data = app1_model.lifecycle_data
333+
expect(lifecycle_data.buildpacks.first).to include('https://github.com/cloudfoundry/nginx-buildpack.git')
334+
end
335+
end
336+
end
337+
end
338+
221339
context 'service bindings' do
222340
let(:client) { instance_double(VCAP::Services::ServiceBrokers::V2::Client, bind: {}) }
223341

0 commit comments

Comments
 (0)