mirror of https://github.com/grpc/grpc.git
Python PSM Security Interop Client+Server (#25991)
* Add channelz to security client * Make client changes. Some config needs to be reverted * Add missing security field to channelz Socket * Add test * Fix test * Remove whitespaces for windows * Remove local_certificate check from PSM security tests * Byte pack Python channelz IPs * Move address packing to Core * WIP * Unbork security tests * Python interop server * Turn up server logging * Clean up PR * Clean up some more * Yapf * Fix up docker images * Swap fillllllles! * Add more robust boolean arg parsing * Use bool_arg parsing in server as well * Add copyright Co-authored-by: Yash Tibrewal <yashkt@google.com>pull/26073/head
parent
2fc80c234b
commit
53c72c5936
7 changed files with 332 additions and 7 deletions
@ -0,0 +1,25 @@ |
|||||||
|
FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750 |
||||||
|
|
||||||
|
RUN apt-get update -y && \ |
||||||
|
apt-get install -y \ |
||||||
|
build-essential \ |
||||||
|
clang \ |
||||||
|
python3 \ |
||||||
|
python3-dev |
||||||
|
|
||||||
|
WORKDIR /workdir |
||||||
|
|
||||||
|
RUN ln -s /usr/bin/python3 /usr/bin/python |
||||||
|
RUN mkdir /artifacts |
||||||
|
|
||||||
|
COPY . . |
||||||
|
RUN tools/bazel build -c dbg //src/python/grpcio_tests/tests_py3_only/interop:xds_interop_client |
||||||
|
RUN cp -rL /workdir/bazel-bin/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_client* /artifacts/ |
||||||
|
|
||||||
|
FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750 |
||||||
|
COPY --from=0 /artifacts ./ |
||||||
|
|
||||||
|
RUN apt-get update -y && apt-get install -y python3 |
||||||
|
RUN ln -s /usr/bin/python3 /usr/bin/python |
||||||
|
|
||||||
|
ENTRYPOINT ["/xds_interop_client"] |
@ -0,0 +1,25 @@ |
|||||||
|
FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750 |
||||||
|
|
||||||
|
RUN apt-get update -y && \ |
||||||
|
apt-get install -y \ |
||||||
|
build-essential \ |
||||||
|
clang \ |
||||||
|
python3 \ |
||||||
|
python3-dev |
||||||
|
|
||||||
|
WORKDIR /workdir |
||||||
|
|
||||||
|
RUN ln -s /usr/bin/python3 /usr/bin/python |
||||||
|
RUN mkdir /artifacts |
||||||
|
|
||||||
|
COPY . . |
||||||
|
RUN tools/bazel build -c dbg //src/python/grpcio_tests/tests_py3_only/interop:xds_interop_server |
||||||
|
RUN cp -rL /workdir/bazel-bin/src/python/grpcio_tests/tests_py3_only/interop/xds_interop_server* /artifacts/ |
||||||
|
|
||||||
|
FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750 |
||||||
|
COPY --from=0 /artifacts ./ |
||||||
|
|
||||||
|
RUN apt-get update -y && apt-get install -y python3 |
||||||
|
RUN ln -s /usr/bin/python3 /usr/bin/python |
||||||
|
|
||||||
|
ENTRYPOINT ["/xds_interop_server"] |
@ -0,0 +1,28 @@ |
|||||||
|
# Copyright 2021 The gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
set -x |
||||||
|
|
||||||
|
VERSION=$(git rev-parse HEAD) |
||||||
|
|
||||||
|
PROJECT=grpc-testing |
||||||
|
TAG=gcr.io/${PROJECT}/python_xds_interop_client:$VERSION |
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." |
||||||
|
|
||||||
|
docker build \ |
||||||
|
-t ${TAG} \ |
||||||
|
-f src/python/grpcio_tests/tests_py3_only/interop/Dockerfile.client \ |
||||||
|
. |
@ -0,0 +1,28 @@ |
|||||||
|
# Copyright 2021 The gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
set -x |
||||||
|
|
||||||
|
VERSION=$(git rev-parse HEAD) |
||||||
|
|
||||||
|
PROJECT=grpc-testing |
||||||
|
TAG=gcr.io/${PROJECT}/python_xds_interop_server:$VERSION |
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." |
||||||
|
|
||||||
|
docker build \ |
||||||
|
-t ${TAG} \ |
||||||
|
-f src/python/grpcio_tests/tests_py3_only/interop/Dockerfile.server \ |
||||||
|
. |
@ -0,0 +1,178 @@ |
|||||||
|
# Copyright 2021 The gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
|
||||||
|
import argparse |
||||||
|
import logging |
||||||
|
import signal |
||||||
|
import threading |
||||||
|
import time |
||||||
|
import socket |
||||||
|
import sys |
||||||
|
|
||||||
|
from typing import DefaultDict, Dict, List, Mapping, Set, Sequence, Tuple |
||||||
|
import collections |
||||||
|
|
||||||
|
from concurrent import futures |
||||||
|
|
||||||
|
import grpc |
||||||
|
from grpc_channelz.v1 import channelz |
||||||
|
from grpc_channelz.v1 import channelz_pb2 |
||||||
|
from grpc_health.v1 import health_pb2, health_pb2_grpc |
||||||
|
from grpc_health.v1 import health as grpc_health |
||||||
|
from grpc_reflection.v1alpha import reflection |
||||||
|
|
||||||
|
from src.proto.grpc.testing import test_pb2 |
||||||
|
from src.proto.grpc.testing import test_pb2_grpc |
||||||
|
from src.proto.grpc.testing import messages_pb2 |
||||||
|
from src.proto.grpc.testing import empty_pb2 |
||||||
|
|
||||||
|
# NOTE: This interop server is not fully compatible with all xDS interop tests. |
||||||
|
# It currently only implements enough functionality to pass the xDS security |
||||||
|
# tests. |
||||||
|
|
||||||
|
_LISTEN_HOST = "[::]" |
||||||
|
|
||||||
|
_THREAD_POOL_SIZE = 256 |
||||||
|
|
||||||
|
logger = logging.getLogger() |
||||||
|
console_handler = logging.StreamHandler() |
||||||
|
formatter = logging.Formatter(fmt='%(asctime)s: %(levelname)-8s %(message)s') |
||||||
|
console_handler.setFormatter(formatter) |
||||||
|
logger.addHandler(console_handler) |
||||||
|
|
||||||
|
|
||||||
|
class TestService(test_pb2_grpc.TestServiceServicer): |
||||||
|
|
||||||
|
def __init__(self, server_id, hostname): |
||||||
|
self._server_id = server_id |
||||||
|
self._hostname = hostname |
||||||
|
|
||||||
|
def EmptyCall(self, _: empty_pb2.Empty, |
||||||
|
context: grpc.ServicerContext) -> empty_pb2.Empty: |
||||||
|
return empty_pb2.Empty() |
||||||
|
|
||||||
|
def UnaryCall(self, request: messages_pb2.SimpleRequest, |
||||||
|
context: grpc.ServicerContext) -> messages_pb2.SimpleResponse: |
||||||
|
response = messages_pb2.SimpleResponse() |
||||||
|
response.server_id = self._server_id |
||||||
|
response.hostname = self._hostname |
||||||
|
return response |
||||||
|
|
||||||
|
|
||||||
|
def _configure_maintenance_server(server: grpc.Server, |
||||||
|
maintenance_port: int) -> None: |
||||||
|
channelz.add_channelz_servicer(server) |
||||||
|
listen_address = f"{_LISTEN_HOST}:{maintenance_port}" |
||||||
|
server.add_insecure_port(listen_address) |
||||||
|
health_servicer = grpc_health.HealthServicer( |
||||||
|
experimental_non_blocking=True, |
||||||
|
experimental_thread_pool=futures.ThreadPoolExecutor( |
||||||
|
max_workers=_THREAD_POOL_SIZE)) |
||||||
|
|
||||||
|
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server) |
||||||
|
SERVICE_NAMES = ( |
||||||
|
test_pb2.DESCRIPTOR.services_by_name["TestService"].full_name, |
||||||
|
health_pb2.DESCRIPTOR.services_by_name["Health"].full_name, |
||||||
|
channelz_pb2.DESCRIPTOR.services_by_name["Channelz"].full_name, |
||||||
|
reflection.SERVICE_NAME, |
||||||
|
) |
||||||
|
for service in SERVICE_NAMES: |
||||||
|
health_servicer.set(service, health_pb2.HealthCheckResponse.SERVING) |
||||||
|
reflection.enable_server_reflection(SERVICE_NAMES, server) |
||||||
|
|
||||||
|
|
||||||
|
def _configure_test_server(server: grpc.Server, port: int, secure_mode: bool, |
||||||
|
server_id: str) -> None: |
||||||
|
test_pb2_grpc.add_TestServiceServicer_to_server( |
||||||
|
TestService(server_id, socket.gethostname()), server) |
||||||
|
listen_address = f"{_LISTEN_HOST}:{port}" |
||||||
|
if not secure_mode: |
||||||
|
server.add_insecure_port(listen_address) |
||||||
|
else: |
||||||
|
logger.info("Running with xDS Server credentials") |
||||||
|
server_fallback_creds = grpc.insecure_server_credentials() |
||||||
|
server_creds = grpc.xds_server_credentials(server_fallback_creds) |
||||||
|
server.add_secure_port(listen_address, server_creds) |
||||||
|
|
||||||
|
|
||||||
|
def _run(port: int, maintenance_port: int, secure_mode: bool, |
||||||
|
server_id: str) -> None: |
||||||
|
if port == maintenance_port: |
||||||
|
server = grpc.server( |
||||||
|
futures.ThreadPoolExecutor(max_workers=_THREAD_POOL_SIZE)) |
||||||
|
_configure_test_server(server, port, secure_mode, server_id) |
||||||
|
_configure_maintenance_server(server, maintenance_port) |
||||||
|
server.start() |
||||||
|
logger.info("Test server listening on port %d", port) |
||||||
|
logger.info("Maintenance server listening on port %d", maintenance_port) |
||||||
|
server.wait_for_termination() |
||||||
|
else: |
||||||
|
test_server = grpc.server( |
||||||
|
futures.ThreadPoolExecutor(max_workers=_THREAD_POOL_SIZE), |
||||||
|
xds=secure_mode) |
||||||
|
_configure_test_server(test_server, port, secure_mode, server_id) |
||||||
|
test_server.start() |
||||||
|
logger.info("Test server listening on port %d", port) |
||||||
|
maintenance_server = grpc.server( |
||||||
|
futures.ThreadPoolExecutor(max_workers=_THREAD_POOL_SIZE)) |
||||||
|
_configure_maintenance_server(maintenance_server, maintenance_port) |
||||||
|
maintenance_server.start() |
||||||
|
logger.info("Maintenance server listening on port %d", maintenance_port) |
||||||
|
test_server.wait_for_termination() |
||||||
|
maintenance_server.wait_for_termination() |
||||||
|
|
||||||
|
|
||||||
|
def bool_arg(arg: str) -> bool: |
||||||
|
if arg.lower() in ("true", "yes", "y"): |
||||||
|
return True |
||||||
|
elif arg.lower() in ("false", "no", "n"): |
||||||
|
return False |
||||||
|
else: |
||||||
|
raise argparse.ArgumentTypeError(f"Could not parse '{arg}' as a bool.") |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
parser = argparse.ArgumentParser( |
||||||
|
description="Run Python xDS interop server.") |
||||||
|
parser.add_argument("--port", |
||||||
|
type=int, |
||||||
|
default=8080, |
||||||
|
help="Port for test server.") |
||||||
|
parser.add_argument("--maintenance_port", |
||||||
|
type=int, |
||||||
|
default=8080, |
||||||
|
help="Port for servers besides test server.") |
||||||
|
parser.add_argument( |
||||||
|
"--secure_mode", |
||||||
|
type=bool_arg, |
||||||
|
default="False", |
||||||
|
help="If specified, uses xDS to retrieve server credentials.") |
||||||
|
parser.add_argument("--server_id", |
||||||
|
type=str, |
||||||
|
default="python_server", |
||||||
|
help="The server ID to return in responses..") |
||||||
|
parser.add_argument('--verbose', |
||||||
|
help='verbose log output', |
||||||
|
default=False, |
||||||
|
action='store_true') |
||||||
|
args = parser.parse_args() |
||||||
|
if args.verbose: |
||||||
|
logger.setLevel(logging.DEBUG) |
||||||
|
else: |
||||||
|
logger.setLevel(logging.INFO) |
||||||
|
if args.secure_mode and args.port == args.maintenance_port: |
||||||
|
raise ValueError( |
||||||
|
"--port and --maintenance_port must not be the same when --secure_mode is set." |
||||||
|
) |
||||||
|
_run(args.port, args.maintenance_port, args.secure_mode, args.server_id) |
Loading…
Reference in new issue