diff --git a/Dockerfile.QA b/Dockerfile.QA index c5a6390ae3..85bb098cf2 100644 --- a/Dockerfile.QA +++ b/Dockerfile.QA @@ -76,6 +76,8 @@ RUN mkdir -p qa/common && \ cp -r /workspace/docs/examples/model_repository/simple qa/L0_http/models && \ cp -r /workspace/docs/examples/model_repository/inception_graphdef qa/L0_http/models && \ cp -r /workspace/docs/examples/model_repository/simple_string qa/L0_http/models && \ + mkdir qa/L0_https/models && \ + cp -r docs/examples/model_repository/simple qa/L0_https/models/. && \ cp -r /workspace/docs/examples/ensemble_model_repository qa/L0_perf_client/ && \ cp /opt/tritonserver/bin/simple qa/L0_simple_lib/. && \ cp /opt/tritonserver/bin/memory_alloc qa/L0_io/. && \ @@ -267,6 +269,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3-setuptools \ swig \ golang-go \ + nginx \ protobuf-compiler && \ rm -rf /var/lib/apt/lists/* diff --git a/qa/L0_https/nginx.conf b/qa/L0_https/nginx.conf new file mode 100644 index 0000000000..e3a78b14e1 --- /dev/null +++ b/qa/L0_https/nginx.conf @@ -0,0 +1,38 @@ +# Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +server { + listen 443 ssl; + server_name localhost; + + ssl_certificate /etc/nginx/cert.crt; + ssl_certificate_key /etc/nginx/cert.key; + + location / { + proxy_pass http://localhost:8000; + proxy_http_version 1.1; + } +} diff --git a/qa/L0_https/test.sh b/qa/L0_https/test.sh new file mode 100644 index 0000000000..3801c8aeb3 --- /dev/null +++ b/qa/L0_https/test.sh @@ -0,0 +1,109 @@ +#!/bin/bash +# Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +REPO_VERSION=${NVIDIA_TRITON_SERVER_VERSION} +if [ "$#" -ge 1 ]; then + REPO_VERSION=$1 +fi +if [ -z "$REPO_VERSION" ]; then + echo -e "Repository version must be specified" + echo -e "\n***\n*** Test Failed\n***" + exit 1 +fi + +export CUDA_VISIBLE_DEVICES=0 + +RET=0 + +SIMPLE_INFER_CLIENT_PY=../clients/simple_http_infer_client.py + +NGINX_CONF=`pwd`/nginx.conf +CLIENT_LOG=`pwd`/client.log +DATADIR=`pwd`/models +SERVER=/opt/tritonserver/bin/tritonserver +SERVER_ARGS="--model-repository=$DATADIR" +source ../common/util.sh + + +# Generate a passphrase +openssl rand -base64 48 > passphrase.txt + +# Generate a Private Key +openssl genrsa -aes128 -passout file:passphrase.txt -out server.key 2048 + +# Generate a CSR (Certificate Signing Request) +openssl req -new -passin file:passphrase.txt -key server.key -out server.csr \ + -subj "/C=FR/O=krkr/OU=Domain Control Validated/CN=*.krkr.io" + +# Remove Passphrase from Key +cp server.key server.key.org +openssl rsa -in server.key.org -passin file:passphrase.txt -out server.key + +# Generating a Self-Signed Certificate for 100 years +openssl x509 -req -days 36500 -in server.csr -signkey server.key -out server.crt + +mv server.crt /etc/nginx/cert.crt +mv server.key /etc/nginx/cert.key + +run_server +if [ "$SERVER_PID" == "0" ]; then + echo -e "\n***\n*** Failed to start $SERVER\n***" + cat $SERVER_LOG + exit 1 +fi + +# Setup the new configuration for the proxy. The HTTPS traffic will be +# redirected to the running instance of server at localhost:8000 +cp ${NGINX_CONF} /etc/nginx/sites-available/default + +# Start the proxy server +service nginx restart + +set +e + +# Test basic inference with https +python $SIMPLE_INFER_CLIENT_PY -v -u localhost --ssl >> ${CLIENT_LOG}.ssl_infer 2>&1 +if [ $? -ne 0 ]; then + cat ${CLIENT_LOG}.ssl_infer + RET=1 +fi + +set -e + +kill $SERVER_PID +wait $SERVER_PID + +# Stop the proxy server +service nginx stop + +if [ $RET -eq 0 ]; then + echo -e "\n***\n*** Test Passed\n***" +else + echo -e "\n***\n*** Test FAILED\n***" +fi + +exit $RET diff --git a/src/clients/python/examples/simple_http_infer_client.py b/src/clients/python/examples/simple_http_infer_client.py index 8e53213db7..66aa163e32 100644 --- a/src/clients/python/examples/simple_http_infer_client.py +++ b/src/clients/python/examples/simple_http_infer_client.py @@ -28,6 +28,7 @@ import argparse import numpy as np import sys +import gevent.ssl import tritonhttpclient from tritonclientutils import InferenceServerException @@ -94,6 +95,12 @@ def test_infer_no_outputs(model_name, input0_data, input1_data, headers=None): required=False, default='localhost:8000', help='Inference server URL. Default is localhost:8000.') + parser.add_argument('-s', + '--ssl', + action="store_true", + required=False, + default=False, + help='Enable encrypted link to the server using HTTPS') parser.add_argument('-H', dest='http_headers', metavar="HTTP_HEADER", required=False, action='append', help='HTTP headers to add to inference server requests. ' + @@ -101,7 +108,14 @@ def test_infer_no_outputs(model_name, input0_data, input1_data, headers=None): FLAGS = parser.parse_args() try: - triton_client = tritonhttpclient.InferenceServerClient(url=FLAGS.url, + if FLAGS.ssl: + triton_client = tritonhttpclient.InferenceServerClient(url=FLAGS.url, + verbose=FLAGS.verbose, + ssl=True, + ssl_context_factory=gevent.ssl._create_unverified_context, + insecure=True) + else: + triton_client = tritonhttpclient.InferenceServerClient(url=FLAGS.url, verbose=FLAGS.verbose) except Exception as e: print("channel creation failed: " + str(e)) diff --git a/src/clients/python/library/httpclient.py b/src/clients/python/library/httpclient.py index 42396d3103..a398cea0fc 100755 --- a/src/clients/python/library/httpclient.py +++ b/src/clients/python/library/httpclient.py @@ -143,6 +143,21 @@ class InferenceServerClient: for handling asynchronous inference requests. Default value is None, which means there will be no restriction on the number of greenlets created. + ssl : bool + If True, channels the requests to encrypted https scheme. + Default value is False. + ssl_options : dict + Any options supported by `ssl.wrap_socket` specified as + dictionary. The argument is ignored if 'ssl' is specified + False. + ssl_context_factory : SSLContext callable + It must be a callbable that returns a SSLContext. The default + value is None which use `ssl.create_default_context`. The + argument is ignored if 'ssl' is specified False. + insecure : bool + If True, then does not match the host name with the certificate. + Default value is False. The argument is ignored if 'ssl' is + specified False. Raises ------ @@ -157,13 +172,21 @@ def __init__(self, connection_timeout=60.0, network_timeout=60.0, verbose=False, - max_greenlets=None): - self._parsed_url = URL("http://" + url) + max_greenlets=None, + ssl=False, + ssl_options=None, + ssl_context_factory=None, + insecure=False): + scheme = "https://" if ssl else "http://" + self._parsed_url = URL(scheme + url) self._client_stub = HTTPClient.from_url( self._parsed_url, concurrency=connection_count, connection_timeout=connection_timeout, - network_timeout=network_timeout) + network_timeout=network_timeout, + ssl_options=ssl_options, + ssl_context_factory=ssl_context_factory, + insecure=insecure) self._pool = gevent.pool.Pool(max_greenlets) self._verbose = verbose