github.com/nginxinc/kubernetes-ingress@v1.12.5/tests/suite/test_app_protect_grpc.py (about)

     1  import grpc
     2  import pytest
     3  from settings import TEST_DATA, DEPLOYMENTS
     4  from suite.custom_resources_utils import (
     5      create_ap_logconf_from_yaml,
     6      create_ap_policy_from_yaml,
     7      delete_ap_policy,
     8      delete_ap_logconf,
     9  )
    10  from suite.grpc.helloworld_pb2 import HelloRequest
    11  from suite.grpc.helloworld_pb2_grpc import GreeterStub
    12  
    13  from suite.resources_utils import (
    14      wait_before_test,
    15      create_example_app,
    16      wait_until_all_pods_are_ready,
    17      create_items_from_yaml,
    18      delete_items_from_yaml,
    19      delete_common_app,
    20      replace_configmap_from_yaml,
    21      create_ingress_with_ap_annotations,
    22      wait_before_test,
    23      get_file_contents,
    24      get_service_endpoint,
    25  )
    26  from suite.ssl_utils import get_certificate
    27  from suite.yaml_utils import get_first_ingress_host_from_yaml
    28  
    29  log_loc = f"/var/log/messages"
    30  valid_resp_txt = "Hello"
    31  invalid_resp_text = "The request was rejected. Please consult with your administrator."
    32  
    33  class BackendSetup:
    34      """
    35      Encapsulate the example details.
    36  
    37      Attributes:
    38          ingress_host (str):
    39      """
    40  
    41      def __init__(self, ingress_host, ip, port_ssl):
    42          self.ingress_host = ingress_host
    43          self.ip = ip
    44          self.port_ssl = port_ssl
    45  
    46  
    47  @pytest.fixture(scope="function")
    48  def backend_setup(request, kube_apis, ingress_controller_endpoint, ingress_controller_prerequisites, test_namespace) -> BackendSetup:
    49      """
    50      Deploy a simple application and AppProtect manifests.
    51  
    52      :param request: pytest fixture
    53      :param kube_apis: client apis
    54      :param ingress_controller_endpoint: public endpoint
    55      :param test_namespace:
    56      :return: BackendSetup
    57      """
    58      print("------------------------- Replace ConfigMap with HTTP2 -------------------------")
    59      replace_configmap_from_yaml(kube_apis.v1,
    60                              ingress_controller_prerequisites.config_map['metadata']['name'],
    61                              ingress_controller_prerequisites.namespace,
    62                              f"{TEST_DATA}/appprotect/grpc/nginx-config.yaml")
    63  
    64      policy = request.param["policy"]
    65      print("------------------------- Deploy backend application -------------------------")
    66      create_example_app(kube_apis, "grpc", test_namespace)
    67      wait_until_all_pods_are_ready(kube_apis.v1, test_namespace)
    68  
    69      print("------------------------- Deploy Secret -----------------------------")
    70      src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml"
    71      create_items_from_yaml(kube_apis, src_sec_yaml, test_namespace)
    72  
    73      print("------------------------- Deploy logconf -----------------------------")
    74      src_log_yaml = f"{TEST_DATA}/appprotect/logconf.yaml"
    75      log_name = create_ap_logconf_from_yaml(kube_apis.custom_objects, src_log_yaml, test_namespace)
    76  
    77      print(f"------------------------- Deploy appolicy: {policy} ---------------------------")
    78      src_pol_yaml = f"{TEST_DATA}/appprotect/grpc/{policy}.yaml"
    79      pol_name = create_ap_policy_from_yaml(kube_apis.custom_objects, src_pol_yaml, test_namespace)
    80  
    81      print("------------------------- Deploy Syslog -----------------------------")
    82      src_syslog_yaml = f"{TEST_DATA}/appprotect/syslog.yaml"
    83      create_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace)
    84      syslog_ep = get_service_endpoint(kube_apis, "syslog-svc", test_namespace)
    85      print(syslog_ep)
    86      print("------------------------- Deploy ingress -----------------------------")
    87      src_ing_yaml = f"{TEST_DATA}/appprotect/grpc/ingress.yaml"
    88      create_ingress_with_ap_annotations(kube_apis, src_ing_yaml, test_namespace, policy, "True", "True", f"{syslog_ep}:514")
    89      ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml)
    90      wait_before_test(40)
    91  
    92      def fin():
    93          print("Clean up:")
    94          delete_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace)
    95          delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace)
    96          delete_ap_policy(kube_apis.custom_objects, pol_name, test_namespace)
    97          delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace)
    98          delete_common_app(kube_apis, "grpc", test_namespace)
    99          delete_items_from_yaml(kube_apis, src_sec_yaml, test_namespace)
   100          replace_configmap_from_yaml(kube_apis.v1,
   101                          ingress_controller_prerequisites.config_map['metadata']['name'],
   102                          ingress_controller_prerequisites.namespace,
   103                          f"{DEPLOYMENTS}/common/nginx-config.yaml")
   104  
   105      request.addfinalizer(fin)
   106  
   107      return BackendSetup(ingress_host, ingress_controller_endpoint.public_ip, ingress_controller_endpoint.port_ssl)
   108  
   109  
   110  @pytest.mark.skip_for_nginx_oss
   111  @pytest.mark.appprotect
   112  @pytest.mark.parametrize(
   113      "crd_ingress_controller_with_ap",
   114      [{"extra_args": [f"-enable-custom-resources", f"-enable-app-protect"]}],
   115      indirect=["crd_ingress_controller_with_ap"],
   116  )
   117  class TestAppProtect:
   118      @pytest.mark.smoke
   119      @pytest.mark.parametrize("backend_setup", [{"policy": "grpc-block-sayhello"}], indirect=True)
   120      def test_responses_grpc_block(
   121          self, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace
   122      ):
   123          """
   124          Test grpc-block-hello AppProtect policy: Blocks /sayhello gRPC method only
   125          Client sends request to /sayhello
   126          """
   127          syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name
   128  
   129          # we need to get the cert so that it can be used in credentials in grpc.secure_channel to verify itself.
   130          # without verification, we will not be able to use the channel
   131          cert = get_certificate(backend_setup.ip, backend_setup.ingress_host, backend_setup.port_ssl)
   132  
   133          target = f'{backend_setup.ip}:{backend_setup.port_ssl}'
   134          credentials = grpc.ssl_channel_credentials(root_certificates=cert.encode())
   135  
   136          # this option is necessary to set the SNI of a gRPC connection and it only works with grpc.secure_channel.
   137          # also, the TLS cert for the Ingress must have the CN equal to backend_setup.ingress_host
   138          options = (('grpc.ssl_target_name_override', backend_setup.ingress_host),)
   139  
   140          with grpc.secure_channel(target, credentials, options) as channel:
   141              stub = GreeterStub(channel)
   142              ex = ""
   143              try:
   144                  stub.SayHello(HelloRequest(name=backend_setup.ip))
   145                  pytest.fail("RPC error was expected during call, exiting...")
   146              except grpc.RpcError as e:
   147                  # grpc.RpcError is also grpc.Call https://grpc.github.io/grpc/python/grpc.html#client-side-context
   148                  ex = e.details()
   149                  print(ex)
   150                  
   151          log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace)
   152          assert (
   153              invalid_resp_text in ex and
   154              'ASM:attack_type="Directory Indexing"' in log_contents and
   155              'violations="Illegal gRPC method"' in log_contents and
   156              'severity="Error"' in log_contents and
   157              'outcome="REJECTED"' in log_contents
   158          )
   159  
   160      @pytest.mark.parametrize("backend_setup", [{"policy": "grpc-block-saygoodbye"}], indirect=True)
   161      def test_responses_grpc_allow(
   162          self, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace, ingress_controller_endpoint
   163      ):
   164          """
   165          Test grpc-block-goodbye AppProtect policy: Blocks /saygoodbye gRPC method only
   166          Client sends request to /sayhello thus should pass
   167          """
   168          syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name
   169          cert = get_certificate(backend_setup.ip, backend_setup.ingress_host, backend_setup.port_ssl)
   170  
   171          target = f'{backend_setup.ip}:{backend_setup.port_ssl}'
   172          credentials = grpc.ssl_channel_credentials(root_certificates=cert.encode())
   173          options = (('grpc.ssl_target_name_override', backend_setup.ingress_host),)
   174  
   175          with grpc.secure_channel(target, credentials, options) as channel:
   176              stub = GreeterStub(channel)
   177              response = ""
   178              try:
   179                  response = stub.SayHello(HelloRequest(name=backend_setup.ip))
   180                  print(response)
   181              except grpc.RpcError as e:
   182                  print(e.details())
   183                  pytest.fail("RPC error was not expected during call, exiting...")
   184                  
   185          log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace)
   186          assert (
   187              valid_resp_txt in response.message and
   188              'ASM:attack_type="N/A"' in log_contents and
   189              'violations="N/A"' in log_contents and
   190              'severity="Informational"' in log_contents and
   191              'outcome="PASSED"' in log_contents
   192          )