diff --git a/app/messages/app_manifest_message.rb b/app/messages/app_manifest_message.rb index 97802f9e361..cfd5e71d0a0 100644 --- a/app/messages/app_manifest_message.rb +++ b/app/messages/app_manifest_message.rb @@ -124,9 +124,12 @@ def manifest_features_update_message end def audit_hash - override_env = original_yaml['env'] ? { 'env' => Presenters::Censorship::PRIVATE_DATA_HIDDEN } : {} - override_cnb = original_yaml['cnb-credentials'] ? { 'cnb-credentials' => Presenters::Censorship::PRIVATE_DATA_HIDDEN } : {} - original_yaml.merge(override_env).merge(override_cnb) + result = original_yaml.dup + result['env'] = Presenters::Censorship::PRIVATE_DATA_HIDDEN if result.key?('env') + result['cnb-credentials'] = Presenters::Censorship::PRIVATE_DATA_HIDDEN if result.key?('cnb-credentials') + obfuscate_buildpack_urls(result) + redact_service_parameters(result) + result end def app_lifecycle_hash @@ -164,6 +167,21 @@ def app_lifecycle_hash attr_reader :original_yaml + def obfuscate_buildpack_urls(result) + result['buildpack'] = CloudController::UrlSecretObfuscator.obfuscate(result['buildpack']) if result['buildpack'] + result['buildpacks'] = result['buildpacks'].map { |b| CloudController::UrlSecretObfuscator.obfuscate(b) } if result['buildpacks'] + end + + def redact_service_parameters(result) + return unless result['services'] + + result['services'] = result['services'].map do |svc| + next svc unless svc.is_a?(Hash) && svc.key?('parameters') + + svc.merge('parameters' => Presenters::Censorship::PRIVATE_DATA_HIDDEN) + end + end + def manifest_buildpack_message @manifest_buildpack_message ||= ManifestBuildpackMessage.new(buildpack:) end diff --git a/app/repositories/service_generic_binding_event_repository.rb b/app/repositories/service_generic_binding_event_repository.rb index 45774ac4514..123520c2ed3 100644 --- a/app/repositories/service_generic_binding_event_repository.rb +++ b/app/repositories/service_generic_binding_event_repository.rb @@ -97,8 +97,9 @@ def record_show(service_binding, user_audit_info) private def censor_request_attributes(request) - attrs = request.dup.stringify_keys + attrs = request.dup.stringify_keys attrs['data'] = Presenters::Censorship::PRIVATE_DATA_HIDDEN if attrs.key?('data') + attrs['parameters'] = Presenters::Censorship::PRIVATE_DATA_HIDDEN if attrs.key?('parameters') attrs end diff --git a/spec/unit/messages/app_manifest_message_spec.rb b/spec/unit/messages/app_manifest_message_spec.rb index b69883767d8..900a59a58c7 100644 --- a/spec/unit/messages/app_manifest_message_spec.rb +++ b/spec/unit/messages/app_manifest_message_spec.rb @@ -1394,6 +1394,78 @@ module VCAP::CloudController expect(message.audit_hash).to eq(expected_hash) end end + + context 'when "buildpack" contains credentials in the URL' do + let(:parsed_yaml) do + { + 'buildpack' => 'https://user:secret@git.example.com/buildpack.git' + } + end + + it 'obfuscates the credentials in the buildpack URL' do + message = AppManifestMessage.create_from_yml(parsed_yaml) + expect(message.audit_hash['buildpack']).to eq('https://***:***@git.example.com/buildpack.git') + end + end + + context 'when "buildpack" does not contain credentials' do + let(:parsed_yaml) { { 'buildpack' => 'ruby_buildpack' } } + + it 'leaves the buildpack value unchanged' do + message = AppManifestMessage.create_from_yml(parsed_yaml) + expect(message.audit_hash['buildpack']).to eq('ruby_buildpack') + end + end + + context 'when "buildpacks" contains credential URLs' do + let(:parsed_yaml) do + { + 'buildpacks' => [ + 'https://user:secret@git.example.com/bp1.git', + 'ruby_buildpack' + ] + } + end + + it 'obfuscates credentials in each buildpack URL' do + message = AppManifestMessage.create_from_yml(parsed_yaml) + expect(message.audit_hash['buildpacks']).to eq([ + 'https://***:***@git.example.com/bp1.git', + 'ruby_buildpack' + ]) + end + end + + context 'when "services" contain parameters' do + let(:parsed_yaml) do + { + 'services' => [ + { 'name' => 'my-service', 'parameters' => { 'key' => 'secret-value' } }, + { 'name' => 'other-service' } + ] + } + end + + it 'redacts parameters from each service binding' do + message = AppManifestMessage.create_from_yml(parsed_yaml) + services = message.audit_hash['services'] + expect(services[0]).to eq({ 'name' => 'my-service', 'parameters' => '[PRIVATE DATA HIDDEN]' }) + expect(services[1]).to eq({ 'name' => 'other-service' }) + end + end + + context 'when "services" is a list of plain strings' do + let(:parsed_yaml) do + { + 'services' => %w[my-service other-service] + } + end + + it 'leaves the services list unchanged' do + message = AppManifestMessage.create_from_yml(parsed_yaml) + expect(message.audit_hash['services']).to eq(%w[my-service other-service]) + end + end end describe '#manifest_process_scale_messages' do diff --git a/spec/unit/repositories/service_generic_binding_event_repository_spec.rb b/spec/unit/repositories/service_generic_binding_event_repository_spec.rb new file mode 100644 index 00000000000..4e39f0f4225 --- /dev/null +++ b/spec/unit/repositories/service_generic_binding_event_repository_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' +require 'repositories/service_generic_binding_event_repository' + +module VCAP::CloudController + module Repositories + RSpec.describe ServiceGenericBindingEventRepository do + let(:user_guid) { 'user-guid' } + let(:user_email) { 'some-email' } + let(:user_name) { 'some-username' } + let(:user_audit_info) { UserAuditInfo.new(user_guid:, user_name:, user_email:) } + let(:service_binding) { create(:service_binding, name: 'some-binding-name') } + let(:repository) { described_class.new(described_class::SERVICE_APP_CREDENTIAL_BINDING) } + + describe '#record_start_create' do + it 'censors metadata.request.parameters' do + request = { 'big' => 'data', 'parameters' => { 'secret' => 'value' } } + + event = repository.record_start_create(service_binding, user_audit_info, request) + + expect(event.metadata[:request]).to eq( + { + 'big' => 'data', + 'parameters' => '[PRIVATE DATA HIDDEN]' + } + ) + end + end + + describe '#record_create' do + it 'censors metadata.request.parameters' do + request = { 'big' => 'data', parameters: { 'secret' => 'value' } } + + event = repository.record_create(service_binding, user_audit_info, request) + + expect(event.metadata[:request]).to eq( + { + 'big' => 'data', + 'parameters' => '[PRIVATE DATA HIDDEN]' + } + ) + end + end + + describe '#record_update' do + it 'censors metadata.request.parameters' do + request = { 'big' => 'data', 'parameters' => { 'secret' => 'value' } } + + event = repository.record_update(service_binding, user_audit_info, request) + + expect(event.metadata[:request]).to eq( + { + 'big' => 'data', + 'parameters' => '[PRIVATE DATA HIDDEN]' + } + ) + end + end + end + end +end