1# Copyright 2020 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Channelz debugging tool for xDS test client/server. 15 16This is intended as a debugging / local development helper and not executed 17as a part of interop test suites. 18 19Typical usage examples: 20 21 # Show channel and server socket pair 22 python -m bin.run_channelz --flagfile=config/local-dev.cfg 23 24 # Evaluate setup for different security configurations 25 python -m bin.run_channelz --flagfile=config/local-dev.cfg --security=tls 26 python -m bin.run_channelz --flagfile=config/local-dev.cfg --security=mtls_error 27 28 # More information and usage options 29 python -m bin.run_channelz --helpfull 30""" 31import hashlib 32 33from absl import app 34from absl import flags 35from absl import logging 36 37from bin.lib import common 38from framework import xds_flags 39from framework import xds_k8s_flags 40from framework.infrastructure import gcp 41from framework.infrastructure import k8s 42from framework.rpc import grpc_channelz 43from framework.test_app import client_app 44from framework.test_app import server_app 45 46# Flags 47_SECURITY = flags.DEFINE_enum('security', 48 default=None, 49 enum_values=[ 50 'mtls', 'tls', 'plaintext', 'mtls_error', 51 'server_authz_error' 52 ], 53 help='Show info for a security setup') 54flags.adopt_module_key_flags(xds_flags) 55flags.adopt_module_key_flags(xds_k8s_flags) 56# Running outside of a test suite, so require explicit resource_suffix. 57flags.mark_flag_as_required(xds_flags.RESOURCE_SUFFIX.name) 58flags.register_validator(xds_flags.SERVER_XDS_PORT.name, 59 lambda val: val > 0, 60 message="Run outside of a test suite, must provide" 61 " the exact port value (must be greater than 0).") 62 63logger = logging.get_absl_logger() 64 65# Type aliases 66_Channel = grpc_channelz.Channel 67_Socket = grpc_channelz.Socket 68_ChannelState = grpc_channelz.ChannelState 69_XdsTestServer = server_app.XdsTestServer 70_XdsTestClient = client_app.XdsTestClient 71 72 73def debug_cert(cert): 74 if not cert: 75 return '<missing>' 76 sha1 = hashlib.sha1(cert) 77 return f'sha1={sha1.hexdigest()}, len={len(cert)}' 78 79 80def debug_sock_tls(tls): 81 return (f'local: {debug_cert(tls.local_certificate)}\n' 82 f'remote: {debug_cert(tls.remote_certificate)}') 83 84 85def get_deployment_pods(k8s_ns, deployment_name): 86 deployment = k8s_ns.get_deployment(deployment_name) 87 return k8s_ns.list_deployment_pods(deployment) 88 89 90def debug_security_setup_negative(test_client): 91 """Debug negative cases: mTLS Error, Server AuthZ error 92 93 1) mTLS Error: Server expects client mTLS cert, 94 but client configured only for TLS. 95 2) AuthZ error: Client does not authorize server because of mismatched 96 SAN name. 97 """ 98 # Client side. 99 client_correct_setup = True 100 channel: _Channel = test_client.wait_for_server_channel_state( 101 state=_ChannelState.TRANSIENT_FAILURE) 102 try: 103 subchannel, *subchannels = list( 104 test_client.channelz.list_channel_subchannels(channel)) 105 except ValueError: 106 print("Client setup fail: subchannel not found. " 107 "Common causes: test client didn't connect to TD; " 108 "test client exhausted retries, and closed all subchannels.") 109 return 110 111 # Client must have exactly one subchannel. 112 logger.debug('Found subchannel, %s', subchannel) 113 if subchannels: 114 client_correct_setup = False 115 print(f'Unexpected subchannels {subchannels}') 116 subchannel_state: _ChannelState = subchannel.data.state.state 117 if subchannel_state is not _ChannelState.TRANSIENT_FAILURE: 118 client_correct_setup = False 119 print('Subchannel expected to be in ' 120 'TRANSIENT_FAILURE, same as its channel') 121 122 # Client subchannel must have no sockets. 123 sockets = list(test_client.channelz.list_subchannels_sockets(subchannel)) 124 if sockets: 125 client_correct_setup = False 126 print(f'Unexpected subchannel sockets {sockets}') 127 128 # Results. 129 if client_correct_setup: 130 print('Client setup pass: the channel ' 131 'to the server has exactly one subchannel ' 132 'in TRANSIENT_FAILURE, and no sockets') 133 134 135def debug_security_setup_positive(test_client, test_server): 136 """Debug positive cases: mTLS, TLS, Plaintext.""" 137 test_client.wait_for_active_server_channel() 138 client_sock: _Socket = test_client.get_active_server_channel_socket() 139 server_sock: _Socket = test_server.get_server_socket_matching_client( 140 client_sock) 141 142 server_tls = server_sock.security.tls 143 client_tls = client_sock.security.tls 144 145 print(f'\nServer certs:\n{debug_sock_tls(server_tls)}') 146 print(f'\nClient certs:\n{debug_sock_tls(client_tls)}') 147 print() 148 149 if server_tls.local_certificate: 150 eq = server_tls.local_certificate == client_tls.remote_certificate 151 print(f'(TLS) Server local matches client remote: {eq}') 152 else: 153 print('(TLS) Not detected') 154 155 if server_tls.remote_certificate: 156 eq = server_tls.remote_certificate == client_tls.local_certificate 157 print(f'(mTLS) Server remote matches client local: {eq}') 158 else: 159 print('(mTLS) Not detected') 160 161 162def debug_basic_setup(test_client, test_server): 163 """Show channel and server socket pair""" 164 test_client.wait_for_active_server_channel() 165 client_sock: _Socket = test_client.get_active_server_channel_socket() 166 server_sock: _Socket = test_server.get_server_socket_matching_client( 167 client_sock) 168 169 logger.debug('Client socket: %s\n', client_sock) 170 logger.debug('Matching server socket: %s\n', server_sock) 171 172 173def main(argv): 174 if len(argv) > 1: 175 raise app.UsageError('Too many command-line arguments.') 176 177 # Must be called before KubernetesApiManager or GcpApiManager init. 178 xds_flags.set_socket_default_timeout_from_flag() 179 180 # Flags. 181 should_port_forward: bool = xds_k8s_flags.DEBUG_USE_PORT_FORWARDING.value 182 is_secure: bool = bool(_SECURITY.value) 183 184 # Setup. 185 gcp_api_manager = gcp.api.GcpApiManager() 186 k8s_api_manager = k8s.KubernetesApiManager(xds_k8s_flags.KUBE_CONTEXT.value) 187 188 # Server. 189 server_namespace = common.make_server_namespace(k8s_api_manager) 190 server_runner = common.make_server_runner( 191 server_namespace, 192 gcp_api_manager, 193 port_forwarding=should_port_forward, 194 secure=is_secure) 195 # Find server pod. 196 server_pod: k8s.V1Pod = common.get_server_pod(server_runner, 197 xds_flags.SERVER_NAME.value) 198 199 # Client 200 client_namespace = common.make_client_namespace(k8s_api_manager) 201 client_runner = common.make_client_runner( 202 client_namespace, 203 gcp_api_manager, 204 port_forwarding=should_port_forward, 205 secure=is_secure) 206 # Find client pod. 207 client_pod: k8s.V1Pod = common.get_client_pod(client_runner, 208 xds_flags.CLIENT_NAME.value) 209 210 # Ensure port forwarding stopped. 211 common.register_graceful_exit(server_runner, client_runner) 212 213 # Create server app for the server pod. 214 test_server: _XdsTestServer = common.get_test_server_for_pod( 215 server_runner, 216 server_pod, 217 test_port=xds_flags.SERVER_PORT.value, 218 secure_mode=is_secure) 219 test_server.set_xds_address(xds_flags.SERVER_XDS_HOST.value, 220 xds_flags.SERVER_XDS_PORT.value) 221 222 # Create client app for the client pod. 223 test_client: _XdsTestClient = common.get_test_client_for_pod( 224 client_runner, client_pod, server_target=test_server.xds_uri) 225 226 with test_client, test_server: 227 if _SECURITY.value in ('mtls', 'tls', 'plaintext'): 228 debug_security_setup_positive(test_client, test_server) 229 elif _SECURITY.value in ('mtls_error', 'server_authz_error'): 230 debug_security_setup_negative(test_client) 231 else: 232 debug_basic_setup(test_client, test_server) 233 234 logger.info('SUCCESS!') 235 236 237if __name__ == '__main__': 238 app.run(main) 239