call_credentials_timeout_test.rb 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #!/usr/bin/env ruby
  2. #
  3. # Copyright 2016 gRPC authors.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. this_dir = File.expand_path(File.dirname(__FILE__))
  17. protos_lib_dir = File.join(this_dir, 'lib')
  18. grpc_lib_dir = File.join(File.dirname(this_dir), 'lib')
  19. $LOAD_PATH.unshift(grpc_lib_dir) unless $LOAD_PATH.include?(grpc_lib_dir)
  20. $LOAD_PATH.unshift(protos_lib_dir) unless $LOAD_PATH.include?(protos_lib_dir)
  21. $LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir)
  22. require 'grpc'
  23. require 'end2end_common'
  24. def create_channel_creds
  25. test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
  26. files = ['ca.pem', 'client.key', 'client.pem']
  27. creds = files.map { |f| File.open(File.join(test_root, f)).read }
  28. GRPC::Core::ChannelCredentials.new(creds[0], creds[1], creds[2])
  29. end
  30. def client_cert
  31. test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
  32. cert = File.open(File.join(test_root, 'client.pem')).read
  33. fail unless cert.is_a?(String)
  34. cert
  35. end
  36. def create_server_creds
  37. test_root = File.join(File.dirname(__FILE__), '..', 'spec', 'testdata')
  38. GRPC.logger.info("test root: #{test_root}")
  39. files = ['ca.pem', 'server1.key', 'server1.pem']
  40. creds = files.map { |f| File.open(File.join(test_root, f)).read }
  41. GRPC::Core::ServerCredentials.new(
  42. creds[0],
  43. [{ private_key: creds[1], cert_chain: creds[2] }],
  44. true) # force client auth
  45. end
  46. # rubocop:disable Metrics/AbcSize
  47. # rubocop:disable Metrics/MethodLength
  48. def main
  49. server_runner = ServerRunner.new(EchoServerImpl)
  50. server_runner.server_creds = create_server_creds
  51. server_port = server_runner.run
  52. channel_args = {
  53. GRPC::Core::Channel::SSL_TARGET => 'foo.test.google.fr'
  54. }
  55. token_fetch_attempts = MutableValue.new(0)
  56. token_fetch_attempts_mu = Mutex.new
  57. jwt_aud_uri_extraction_success_count = MutableValue.new(0)
  58. jwt_aud_uri_extraction_success_count_mu = Mutex.new
  59. expected_jwt_aud_uri = 'https://foo.test.google.fr/echo.EchoServer'
  60. jwt_aud_uri_failure_values = []
  61. times_out_first_time_auth_proc = proc do |args|
  62. # We check the value of jwt_aud_uri not necessarily as a test for
  63. # the correctness of jwt_aud_uri w.r.t. its expected semantics, but
  64. # more for as an indirect way to check for memory corruption.
  65. jwt_aud_uri_extraction_success_count_mu.synchronize do
  66. if args[:jwt_aud_uri] == expected_jwt_aud_uri
  67. jwt_aud_uri_extraction_success_count.value += 1
  68. else
  69. jwt_aud_uri_failure_values << args[:jwt_aud_uri]
  70. end
  71. end
  72. token_fetch_attempts_mu.synchronize do
  73. old_val = token_fetch_attempts.value
  74. token_fetch_attempts.value += 1
  75. if old_val.zero?
  76. STDERR.puts 'call creds plugin sleeping for 4 seconds'
  77. sleep 4
  78. STDERR.puts 'call creds plugin done with 4 second sleep'
  79. raise 'test exception thrown purposely from call creds plugin'
  80. end
  81. end
  82. { 'authorization' => 'fake_val' }.merge(args)
  83. end
  84. channel_creds = create_channel_creds.compose(
  85. GRPC::Core::CallCredentials.new(times_out_first_time_auth_proc))
  86. stub = Echo::EchoServer::Stub.new("localhost:#{server_port}",
  87. channel_creds,
  88. channel_args: channel_args)
  89. STDERR.puts 'perform a first few RPCs to try to get things into a bad state...'
  90. threads = []
  91. got_at_least_one_failure = MutableValue.new(false)
  92. 2000.times do
  93. threads << Thread.new do
  94. begin
  95. # 2 seconds is chosen as deadline here because it is less than the 4 second
  96. # sleep that the first call creds user callback does. The idea here is that
  97. # a lot of RPCs will be made concurrently all with 2 second deadlines, and they
  98. # will all queue up onto the call creds user callback thread, and will all
  99. # have to wait for the first 4 second sleep to finish. When the deadlines
  100. # of the associated calls fire ~2 seconds in, some of their C-core data
  101. # will have ownership dropped, and they will hit the user-after-free in
  102. # https://github.com/grpc/grpc/issues/19195 if this isn't handled correctly.
  103. stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 2)
  104. rescue GRPC::BadStatus
  105. got_at_least_one_failure.value = true
  106. # We don't care if these RPCs succeed or fail. The purpose of these
  107. # RPCs is just to try to induce a specific use-after-free bug, and to get
  108. # the call credentials callback thread into a bad state.
  109. end
  110. end
  111. end
  112. threads.each(&:join)
  113. unless got_at_least_one_failure.value
  114. fail 'expected at least one of the initial RPCs to fail'
  115. end
  116. # Expect three more RPCs to succeed
  117. STDERR.puts 'now perform another RPC and expect OK...'
  118. stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 10)
  119. STDERR.puts 'now perform another RPC and expect OK...'
  120. stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 10)
  121. STDERR.puts 'now perform another RPC and expect OK...'
  122. stub.echo(Echo::EchoRequest.new(request: 'hello'), deadline: Time.now + 10)
  123. jwt_aud_uri_extraction_success_count_mu.synchronize do
  124. if jwt_aud_uri_extraction_success_count.value < 4
  125. fail "Expected auth metadata plugin callback to be ran with the jwt_aud_uri
  126. parameter matching its expected value at least 4 times (at least 1 out of the 2000
  127. initial expected-to-timeout RPCs should have caused this by now, and all three of the
  128. successful RPCs should have caused this). This test isn't doing what it's meant to do."
  129. end
  130. unless jwt_aud_uri_failure_values.empty?
  131. fail "Expected to get jwt_aud_uri:#{expected_jwt_aud_uri} passed to call creds
  132. user callback every time that it was invoked, but it did not match the expected value
  133. in #{jwt_aud_uri_failure_values.size} invocations. This suggests that either:
  134. a) the expected jwt_aud_uri value is incorrect
  135. b) there is some corruption of the jwt_aud_uri argument
  136. Here are are the values of the jwt_aud_uri parameter that were passed to the call
  137. creds user callback that did not match #{expected_jwt_aud_uri}:
  138. #{jwt_aud_uri_failure_values}"
  139. end
  140. end
  141. server_runner.stop
  142. end
  143. main