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