Skip to content

Commit 2536597

Browse files
authored
Merge pull request #2707 from cloudfoundry/blobstore-id-handling
Blobstore id handling
2 parents 849d600 + e5719a2 commit 2536597

25 files changed

Lines changed: 383 additions & 111 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.DS_Store
22
.bundle
3+
.cursor
34
.idea
45
*.iml
56

src/bosh-director/lib/bosh/director/agent_client.rb

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'bosh/director/agent_message_converter'
2+
require 'bosh/director/blobstore/uuid_validation'
23

34
module Bosh::Director
45
class AgentClient
@@ -317,8 +318,10 @@ def format_exception(exception)
317318

318319
if exception['blobstore_id']
319320
blob = download_and_delete_blob(exception['blobstore_id'])
320-
msg += "\n"
321-
msg += blob.to_s
321+
unless blob.nil?
322+
msg += "\n"
323+
msg += blob.to_s
324+
end
322325
end
323326

324327
msg
@@ -334,18 +337,27 @@ def inject_compile_log(response)
334337
response['value']['result'].is_a?(Hash) &&
335338
(blob_id = response['value']['result']['compile_log_id'])
336339
compile_log = download_and_delete_blob(blob_id)
337-
response['value']['result']['compile_log'] = compile_log
340+
response['value']['result']['compile_log'] = compile_log unless compile_log.nil?
338341
end
339342
end
340343

341344
# Downloads blob and ensures it's deleted from the blobstore
342345
# @param [String] blob_id Blob id
343346
# @return [String] Blob contents
344347
def download_and_delete_blob(blob_id)
348+
unless Blobstore::UuidValidation.valid_uuid?(blob_id)
349+
@logger.warn(
350+
"Skipping blob fetch for agent '#{@client_id}' (#{@instance_name}): object id is not a valid UUID",
351+
)
352+
return nil
353+
end
354+
345355
blob = @resource_manager.get_resource(blob_id)
346356
blob
347357
ensure
348-
@resource_manager.delete_resource(blob_id)
358+
if blob_id && Blobstore::UuidValidation.valid_uuid?(blob_id)
359+
@resource_manager.delete_resource(blob_id)
360+
end
349361
end
350362

351363
def handle_message_with_retry(message_name, *args, &blk)

src/bosh-director/lib/bosh/director/api/controllers/deployments_controller.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def initialize(config)
6767
options['manifest_text'] = manifest
6868
else
6969
manifest_hash = validate_manifest_yml(request.body.read)
70+
manifest_hash['name'] = deployment.name
7071
manifest = YAML.dump(manifest_hash)
7172
teams = deployment.teams
7273
latest_cloud_configs = Models::Config.latest_set_for_teams('cloud', *teams)
@@ -112,6 +113,7 @@ def initialize(config)
112113
options['manifest_text'] = manifest
113114
else
114115
manifest_hash = validate_manifest_yml(request.body.read)
116+
manifest_hash['name'] = deployment.name
115117
manifest = YAML.dump(manifest_hash)
116118
teams = deployment.teams
117119
latest_cloud_configs = Models::Config.latest_set_for_teams('cloud', *teams)
@@ -440,9 +442,9 @@ def initialize(config)
440442
end
441443
end
442444

443-
# since authorizer does not look at manifest payload for deployment name
444445
@deployment = Models::Deployment[name: deployment_name]
445446
if @deployment
447+
@permission_authorizer.granted_or_raise(@deployment, :admin, token_scopes)
446448
teams = @deployment.teams
447449
else
448450
teams = Bosh::Director::Models::Team.transform_admin_team_scope_to_teams(token_scopes)
@@ -488,6 +490,12 @@ def initialize(config)
488490
manifest_text = request.body.read
489491
manifest_hash = validate_manifest_yml(manifest_text)
490492

493+
if deployment
494+
manifest_hash['name'] = deployment.name
495+
# ensure diff will show `name` from `deployment.name` and not manifest YAML
496+
manifest_text = YAML.dump(manifest_hash)
497+
end
498+
491499
if deployment
492500
before_manifest = Manifest.load_from_model(deployment, resolve_interpolation: false)
493501
before_manifest.resolve_aliases

src/bosh-director/lib/bosh/director/blobstore/local_client.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'bosh/director/blobstore/uuid_validation'
2+
13
module Bosh::Director
24
module Blobstore
35
class LocalClient < Client
@@ -52,6 +54,10 @@ def required_credential_properties_list
5254
private
5355

5456
def object_file_path(oid)
57+
unless UuidValidation.valid_uuid?(oid)
58+
raise BlobstoreError, "invalid blobstore object id format: #{oid.inspect}"
59+
end
60+
5561
File.join(@blobstore_path, oid)
5662
end
5763
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module Bosh::Director
2+
module Blobstore
3+
# Validates UUIDs are (8-4-4-4-12 hex):
4+
# - Ruby: SecureRandom.uuid
5+
# - Golang: github.com/nu7hatch/gouuid
6+
module UuidValidation
7+
UUID_PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i.freeze
8+
9+
def self.valid_uuid?(value)
10+
UUID_PATTERN.match?(value.to_s)
11+
end
12+
end
13+
end
14+
end

src/bosh-director/lib/bosh/director/deployment_plan/stages/package_compile_stage.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require 'bosh/director/blobstore/uuid_validation'
12
require 'bosh/director/compiled_package_requirement_generator'
23
require 'digest/sha1'
34

@@ -131,6 +132,8 @@ def compile_package(requirement)
131132
end
132133
end
133134

135+
validate_compiled_package_blobstore_id!(task_result['blobstore_id'])
136+
134137
compiled_package = Models::CompiledPackage.create do |p|
135138
p.package = package
136139
p.stemcell_os = stemcell.os
@@ -158,6 +161,13 @@ def prepare_vm(...)
158161

159162
private
160163

164+
def validate_compiled_package_blobstore_id!(blobstore_id)
165+
return if Blobstore::UuidValidation.valid_uuid?(blobstore_id)
166+
167+
raise PackageCompilationInvalidTaskBlobstoreId,
168+
'Compilation task result contained an invalid blobstore object id'
169+
end
170+
161171
def validate_packages(instance_groups_to_compile)
162172
instance_groups_to_compile.each do |instance_group|
163173
instance_group.jobs.each do |job|

src/bosh-director/lib/bosh/director/errors.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ def self.err(error_code, response_code = BAD_REQUEST)
287287
AgentInvalidTaskResult = err(400011)
288288
AgentUnsupportedAction = err(400012)
289289
AgentUploadBlobUnableToOpenFile = err(400013)
290+
AgentTaskInvalidBlobstoreId = err(400014)
290291

291292
# Cloud check task errors
292293
CloudcheckTooManySimilarProblems = err(410001)
@@ -298,6 +299,7 @@ def self.err(error_code, response_code = BAD_REQUEST)
298299
DnsInvalidCanonicalName = err(420001)
299300

300301
# PackageCompilation
302+
PackageCompilationInvalidTaskBlobstoreId = err(430004)
301303
PackageCompilationNotEnoughWorkersForReuse = err(430002)
302304
PackageCompilationNotFound = err(430003)
303305

src/bosh-director/lib/bosh/director/jobs/release/release_job.rb

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'open3'
2+
13
module Bosh::Director
24
class ReleaseJob
35

@@ -11,6 +13,8 @@ def initialize(job_meta, release_model, release_dir, logger)
1113
end
1214

1315
def update
16+
validate_job_meta_identifiers!
17+
1418
unpack
1519

1620
job_manifest = load_manifest
@@ -30,11 +34,11 @@ def update
3034

3135
if job_model.blobstore_id
3236
begin
33-
@logger.info("Deleting blob for job '#{name}/#{@version}' with blobstore_id '#{job_model.blobstore_id}'")
37+
@logger.info("Deleting blob for job '#{name}/#{version}' with blobstore_id '#{job_model.blobstore_id}'")
3438
BlobUtil.delete_blob(job_model.blobstore_id)
3539
job_model.blobstore_id = nil
3640
rescue Bosh::Director::Blobstore::BlobstoreError => e
37-
@logger.info("Error deleting blob for job '#{name}/#{@version}' with blobstore_id '#{job_model.blobstore_id}': #{e.inspect}")
41+
@logger.info("Error deleting blob for job '#{name}/#{version}' with blobstore_id '#{job_model.blobstore_id}': #{e.inspect}")
3842
end
3943
end
4044

@@ -48,16 +52,26 @@ def update
4852
def unpack
4953
FileUtils.mkdir_p(job_dir)
5054

51-
desc = "job '#{name}/#{@version}'"
52-
result = Bosh::Common::Exec.sh("tar -C #{job_dir} -xf #{job_tgz} 2>&1", :on_error => :return)
53-
if result.failed?
55+
desc = "job '#{name}/#{version}'"
56+
out, err, status = Open3.capture3('tar', '-C', job_dir, '-xf', job_tgz)
57+
combined = [out, err].map(&:to_s).join
58+
if status.exitstatus != 0
5459
@logger.error("Extracting #{desc} archive failed in dir #{job_dir}, " +
55-
"tar returned #{result.exit_status}, " +
56-
"output: #{result.output}")
60+
"tar returned #{status.exitstatus}, " +
61+
"output: #{combined}")
5762
raise JobInvalidArchive, "Extracting #{desc} archive failed. Check task debug log for details."
5863
end
5964
end
6065

66+
def validate_job_meta_identifiers!
67+
if !@job_meta['name'].is_a?(String) || !Models::VALID_ID.match?(@job_meta['name'])
68+
raise JobInvalidName, "Invalid job name in release manifest: #{@job_meta['name'].inspect}"
69+
end
70+
if !@job_meta['version'].is_a?(String) || !Models::VALID_ID.match?(@job_meta['version'])
71+
raise ValidationInvalidValue, "Invalid job version in release manifest: #{@job_meta['version'].inspect}"
72+
end
73+
end
74+
6175
def job_tgz
6276
@job_tgz ||= File.join(@release_dir, 'jobs', "#{name}.tgz")
6377
end
@@ -176,5 +190,9 @@ def validate_consume_links(consume_links)
176190
def name
177191
@job_meta['name']
178192
end
193+
194+
def version
195+
@job_meta['version']
196+
end
179197
end
180198
end

src/bosh-director/lib/bosh/director/jobs/update_release.rb

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'pp' # for #pretty_inspect
2+
require 'open3'
23
require 'securerandom'
34
require 'bosh/version/release_version'
45

@@ -71,9 +72,10 @@ def download_remote_release
7172
def extract_release
7273
release_dir = Dir.mktmpdir
7374

74-
result = Bosh::Common::Exec.sh("tar -C #{release_dir} -xf #{release_path} 2>&1", on_error: :return)
75-
if result.failed?
76-
logger.error("Failed to extract release archive '#{release_path}' into dir '#{release_dir}', tar returned #{result.exit_status}, output: #{result.output})")
75+
out, err, status = Open3.capture3('tar', '-C', release_dir, '-xf', release_path)
76+
combined = [out, err].map(&:to_s).join
77+
if status.exitstatus != 0
78+
logger.error("Failed to extract release archive '#{release_path}' into dir '#{release_dir}', tar returned #{status.exitstatus}, output: #{combined}")
7779
FileUtils.rm_rf(release_dir)
7880
raise ReleaseInvalidArchive, 'Extracting release archive failed. Check task debug log for details.'
7981
end
@@ -176,6 +178,29 @@ def normalize_manifest
176178

177179
manifest_packages.each { |p| hash_string_vals(p, 'name', 'version', 'sha1') }
178180
manifest_jobs.each { |j| hash_string_vals(j, 'name', 'version', 'sha1') }
181+
182+
validate_manifest_release_identifiers!
183+
end
184+
185+
def validate_manifest_release_identifiers!
186+
validate_manifest_identifier!(@manifest['name'], 'release name')
187+
validate_manifest_identifier!(@manifest['version'], 'release version')
188+
189+
manifest_packages.each_with_index do |p, i|
190+
validate_manifest_identifier!(p['name'], "package #{i + 1} name")
191+
validate_manifest_identifier!(p['version'], "package #{i + 1} version")
192+
end
193+
194+
manifest_jobs.each_with_index do |j, i|
195+
validate_manifest_identifier!(j['name'], "job #{i + 1} name")
196+
validate_manifest_identifier!(j['version'], "job #{i + 1} version")
197+
end
198+
end
199+
200+
def validate_manifest_identifier!(value, label)
201+
if !value.is_a?(String) || !Models::VALID_ID.match?(value)
202+
raise ValidationInvalidValue, "Invalid #{label} in release manifest: #{value.inspect}"
203+
end
179204
end
180205

181206
# Replace values for keys in a hash with their to_s.

src/bosh-director/lib/bosh/director/jobs/update_release/package_persister.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'open3'
2+
13
module Bosh::Director
24
module Jobs
35
class UpdateRelease < BaseJob
@@ -186,9 +188,10 @@ def delete_package_blob(logger, desc, package)
186188
end
187189

188190
def validate_tgz(logger, tgz, desc)
189-
result = Bosh::Common::Exec.sh("tar -tf #{tgz} 2>&1", on_error: :return)
190-
if result.failed?
191-
logger.error("Extracting #{desc} archive failed, tar returned #{result.exit_status}, output: #{result.output}")
191+
out, err, status = Open3.capture3('tar', '-tf', tgz)
192+
combined = [out, err].map(&:to_s).join
193+
if status.exitstatus != 0
194+
logger.error("Extracting #{desc} archive failed, tar returned #{status.exitstatus}, output: #{combined}")
192195
raise PackageInvalidArchive, "Extracting #{desc} archive failed. Check task debug log for details."
193196
end
194197
end

0 commit comments

Comments
 (0)