Skip to content

Commit 041c899

Browse files
authored
fix: Recursively redact unsafe payloads from logs (#13189)
1 parent f845344 commit 041c899

File tree

2 files changed

+95
-25
lines changed

2 files changed

+95
-25
lines changed

google-apis-core/lib/google/apis/core/http_command.rb

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def decode_response_body(_content_type, body)
264264
# @return [Object] result if no block given
265265
# @yield [result, nil] if block given
266266
def success(result, &block)
267-
logger.debug { sprintf('Success - %s', safe_object_representation(result)) }
267+
logger.debug { sprintf('Success - %s', safe_pretty_representation(result)) }
268268
block.call(result, nil) if block_given?
269269
result
270270
end
@@ -317,7 +317,7 @@ def execute_once(client)
317317
header: request_header,
318318
follow_redirect: true)
319319
logger.debug { @http_res.status }
320-
logger.debug { safe_response_representation @http_res }
320+
logger.debug { safe_single_line_representation @http_res }
321321
response = process_response(@http_res.status.to_i, @http_res.header, @http_res.body)
322322
success(response)
323323
rescue => e
@@ -351,21 +351,37 @@ def allow_form_encoding?
351351
"Google::Apis::SecretmanagerV1beta1::SecretPayload"
352352
]
353353

354-
def safe_object_representation obj
355-
name = obj.class.name
356-
if UNSAFE_CLASS_NAMES.include? name
357-
"#<#{name} (fields redacted)>"
358-
else
359-
PP.pp(obj, "")
354+
module RedactingPPMethods
355+
def pp_object obj
356+
return super unless UNSAFE_CLASS_NAMES.include? obj.class.name
357+
object_address_group obj do
358+
text "(fields redacted)"
359+
end
360360
end
361361
end
362362

363-
def safe_response_representation http_res
364-
if respond_to?(:response_class) && response_class.is_a?(Class) &&
365-
UNSAFE_CLASS_NAMES.include?(response_class.name)
366-
return "#<#{http_res.class.name} (fields redacted)>"
367-
end
368-
http_res.inspect
363+
class RedactingPP < PP
364+
include RedactingPPMethods
365+
end
366+
367+
class RedactingSingleLine < PP::SingleLine
368+
include RedactingPPMethods
369+
end
370+
371+
def safe_pretty_representation obj
372+
out = ""
373+
printer = RedactingPP.new out, 79
374+
printer.guard_inspect_key { printer.pp obj }
375+
printer.flush
376+
out << "\n"
377+
end
378+
379+
def safe_single_line_representation obj
380+
out = ""
381+
printer = RedactingSingleLine.new out
382+
printer.guard_inspect_key { printer.pp obj }
383+
printer.flush
384+
out
369385
end
370386

371387
def opencensus_begin_span

google-apis-core/spec/google/apis/core/http_command_spec.rb

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
module Google
1919
module Apis
20-
module CloudkmsV1
21-
class DecryptResponse
20+
module SecretmanagerV1
21+
class AccessSecretVersionResponse
22+
end
23+
class SecretPayload
2224
end
2325
end
2426
end
@@ -526,23 +528,75 @@ class DecryptResponse
526528
command.execute(client)
527529
end
528530

529-
describe "#safe_object_representation" do
531+
describe "#safe_pretty_representation" do
532+
let(:command) do
533+
Google::Apis::Core::HttpCommand.new(:get, 'https://www.googleapis.com/zoo/animals')
534+
end
535+
536+
it "should show fields in a normal object" do
537+
obj = Google::Apis::SecretmanagerV1::AccessSecretVersionResponse.new
538+
obj.instance_variable_set(:@payload, "hello")
539+
str = command.send(:safe_pretty_representation, obj)
540+
expect(str).to include("@payload")
541+
expect(str).not_to include("(fields redacted)")
542+
expect(str).to include("\n")
543+
end
544+
545+
it "should not show fields in a restricted object" do
546+
obj = Google::Apis::SecretmanagerV1::SecretPayload.new
547+
obj.instance_variable_set(:@payload, "hello")
548+
str = command.send(:safe_pretty_representation, obj)
549+
expect(str).not_to include("@payload")
550+
expect(str).to include("(fields redacted)")
551+
expect(str).to include("\n")
552+
end
553+
554+
it "should not show fields in a nested restricted object" do
555+
obj = Google::Apis::SecretmanagerV1::AccessSecretVersionResponse.new
556+
payload = Google::Apis::SecretmanagerV1::SecretPayload.new
557+
payload.instance_variable_set(:@data, "whoops")
558+
obj.instance_variable_set(:@payload, payload)
559+
str = command.send(:safe_pretty_representation, obj)
560+
expect(str).to include("@payload")
561+
expect(str).not_to include("@data")
562+
expect(str).to include("(fields redacted)")
563+
expect(str).to include("\n")
564+
end
565+
end
566+
567+
describe "#safe_single_line_representation" do
530568
let(:command) do
531569
Google::Apis::Core::HttpCommand.new(:get, 'https://www.googleapis.com/zoo/animals')
532570
end
533571

534572
it "should show fields in a normal object" do
535-
obj = Object.new
536-
obj.instance_variable_set(:@foobar, "hi")
537-
str = command.send(:safe_object_representation, obj)
538-
expect(str).to match /@foobar/
573+
obj = Google::Apis::SecretmanagerV1::AccessSecretVersionResponse.new
574+
obj.instance_variable_set(:@payload, "hello")
575+
str = command.send(:safe_single_line_representation, obj)
576+
expect(str).to include("@payload")
577+
expect(str).not_to include("(fields redacted)")
578+
expect(str).not_to include("\n")
539579
end
540580

541581
it "should not show fields in a restricted object" do
542-
obj = Google::Apis::CloudkmsV1::DecryptResponse.new
543-
obj.instance_variable_set(:@foobar, "hi")
544-
str = command.send(:safe_object_representation, obj)
545-
expect(str).not_to match /@foobar/
582+
obj = Google::Apis::SecretmanagerV1::SecretPayload.new
583+
obj.instance_variable_set(:@payload, "hello")
584+
str = command.send(:safe_single_line_representation, obj)
585+
expect(str).not_to include("@payload")
586+
expect(str).to include("(fields redacted)")
587+
expect(str).not_to include("\n")
588+
end
589+
590+
it "should not show fields in a nested restricted object" do
591+
obj = Google::Apis::SecretmanagerV1::AccessSecretVersionResponse.new
592+
payload = Google::Apis::SecretmanagerV1::SecretPayload.new
593+
payload.instance_variable_set(:@data, "whoops")
594+
obj.instance_variable_set(:@payload, payload)
595+
str = command.send(:safe_single_line_representation, obj)
596+
expect(str).to include("@payload")
597+
expect(str).not_to include("@data")
598+
expect(str).to include("(fields redacted)")
599+
expect(str).not_to include("\n")
546600
end
547601
end
548602
end

0 commit comments

Comments
 (0)