Skip to content

Commit

Permalink
tests: add VSR gRPC tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ciarams87 committed Dec 10, 2021
1 parent 22c967b commit ac14863
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 46 deletions.
17 changes: 17 additions & 0 deletions tests/data/ap-waf-grpc/virtual-server-route-waf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: k8s.nginx.org/v1
kind: VirtualServerRoute
metadata:
name: helloworld.greeter
spec:
host: virtual-server.example.com
upstreams:
- name: grpc1
service: grpc1-svc
port: 50051
type: grpc
subroutes:
- path: "~* /helloworld.greeter/"
action:
pass: grpc1
policies:
- name: waf-policy
11 changes: 11 additions & 0 deletions tests/data/ap-waf-grpc/vsr-virtual-server-spec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: virtual-server-route
spec:
host: virtual-server.example.com
tls:
secret: virtual-server-tls-grpc-secret
routes:
- path: "~* /helloworld.greeter/"
route: helloworld.greeter
184 changes: 138 additions & 46 deletions tests/suite/test_app_protect_waf_policies_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import pytest
from settings import TEST_DATA, DEPLOYMENTS
from suite.fixtures import (
VirtualServerRoute,
VirtualServerSetup,
VirtualServerRouteSetup
)
from suite.grpc.helloworld_pb2 import HelloRequest
from suite.grpc.helloworld_pb2_grpc import GreeterStub
Expand All @@ -18,10 +20,13 @@
delete_common_app,
create_items_from_yaml,
get_service_endpoint,
create_namespace_with_name_from_yaml,
delete_namespace,
)
from suite.vs_vsr_resources_utils import(
delete_virtual_server,
create_virtual_server_from_yaml,
create_v_s_route_from_yaml,
)
from suite.policy_resources_utils import(
delete_policy,
Expand All @@ -37,6 +42,7 @@
from suite.yaml_utils import (
get_first_host_from_yaml,
get_paths_from_vs_yaml,
get_paths_from_vsr_yaml
)

log_loc = f"/var/log/messages"
Expand All @@ -47,7 +53,11 @@
src_vs_sec_yaml = f"{TEST_DATA}/ap-waf-grpc/tls-secret.yaml"
src_log_yaml = f"{TEST_DATA}/ap-waf-grpc/logconf.yaml"
src_syslog_yaml = f"{TEST_DATA}/ap-waf-grpc/syslog.yaml"
std_vs_src = f"{TEST_DATA}/ap-waf-grpc/standard/virtual-server.yaml"
waf_spec_vs_src = f"{TEST_DATA}/ap-waf-grpc/virtual-server-waf-spec.yaml"
waf_route_vs_src = f"{TEST_DATA}/ap-waf-grpc/virtual-server-waf-route.yaml"
waf_subroute_vsr_src = f"{TEST_DATA}/ap-waf-grpc/virtual-server-route-waf.yaml"
vsr_vs_yaml = f"{TEST_DATA}/ap-waf-grpc/vsr-virtual-server-spec.yaml"


@pytest.fixture(scope="class")
Expand All @@ -62,29 +72,65 @@ def appprotect_setup(request, kube_apis, ingress_controller_endpoint, ingress_co
:param test_namespace:
"""
policy_method = request.param["policy"]
vs_or_vsr = request.param["vs_or_vsr"]
vsr = None
try:
print("------------------------- Replace ConfigMap with HTTP2 -------------------------")
replace_configmap_from_yaml(kube_apis.v1,
ingress_controller_prerequisites.config_map['metadata']['name'],
ingress_controller_prerequisites.namespace,
cm_source)
(src_pol_name, vs_name, vs_host, vs_paths) = ap_vs_setup(
kube_apis, test_namespace, policy_method)
if vs_or_vsr == "vs":
(src_pol_name, vs_name, vs_host, vs_paths) = ap_vs_setup(
kube_apis, test_namespace, policy_method)
elif vs_or_vsr == "vsr":
(src_pol_name, vsr_ns, vs_host, vs_name, vsr) = ap_vsr_setup(
kube_apis, test_namespace, policy_method)
wait_before_test(120)
except Exception as ex:
cleanup(
kube_apis, ingress_controller_prerequisites, src_pol_name, test_namespace, vs_name)
kube_apis, ingress_controller_prerequisites, src_pol_name, test_namespace, vs_or_vsr, vs_name, vsr)
def fin():
print("Clean up:")
cleanup(
kube_apis, ingress_controller_prerequisites, src_pol_name, test_namespace, vs_name)
kube_apis, ingress_controller_prerequisites, src_pol_name, test_namespace, vs_or_vsr, vs_name, vsr)

request.addfinalizer(fin)
return VirtualServerSetup(
ingress_controller_endpoint, test_namespace, vs_host, vs_name, vs_paths
)
if vs_or_vsr == "vs":
return VirtualServerSetup(
ingress_controller_endpoint, test_namespace, vs_host, vs_name, vs_paths
)
elif vs_or_vsr == "vsr":
return VirtualServerRouteSetup(
ingress_controller_endpoint, vsr_ns, vs_host, vs_name, vsr, None
)

def ap_vs_setup(kube_apis, test_namespace, policy_method) -> tuple:
src_pol_name, vs_name = ap_generic_setup(
kube_apis, test_namespace, test_namespace,
policy_method, waf_spec_vs_src)
vs_host = get_first_host_from_yaml(waf_spec_vs_src)
vs_paths = get_paths_from_vs_yaml(waf_spec_vs_src)
return (src_pol_name, vs_name, vs_host, vs_paths)

def ap_vsr_setup(kube_apis, test_namespace, policy_method) -> tuple:
print(f"------------------------- Deploy namespace ---------------------------")
vs_routes_ns = "grpcs"
vsr_ns = create_namespace_with_name_from_yaml(
kube_apis.v1, vs_routes_ns, f"{TEST_DATA}/common/ns.yaml")
src_pol_name, vs_name = ap_generic_setup(
kube_apis, vsr_ns, test_namespace, policy_method,
vsr_vs_yaml)
vs_host = get_first_host_from_yaml(vsr_vs_yaml)
print("------------------------- Deploy Virtual Server Route ----------------------------")
vsr_name = create_v_s_route_from_yaml(
kube_apis.custom_objects, waf_subroute_vsr_src, vsr_ns)
vsr_paths = get_paths_from_vsr_yaml(waf_subroute_vsr_src)
vsr = VirtualServerRoute(vsr_ns, vsr_name, vsr_paths)

return (src_pol_name, vsr_ns, vs_host, vs_name, vsr)

def ap_generic_setup(kube_apis, vs_namespace, test_namespace, policy_method, vs_yaml):
src_pol_yaml = f"{TEST_DATA}/ap-waf-grpc/policies/waf-block-{policy_method}.yaml"
print("------------------------- Deploy logconf -----------------------------")
global log_name
Expand All @@ -98,39 +144,40 @@ def ap_vs_setup(kube_apis, test_namespace, policy_method) -> tuple:
wait_before_test(20)
syslog_ep = get_service_endpoint(kube_apis, "syslog-svc", test_namespace)
print("------------------------- Deploy App -----------------------------")
create_example_app(kube_apis, "grpc-vs", test_namespace)
wait_until_all_pods_are_ready(kube_apis.v1, test_namespace)
create_example_app(kube_apis, "grpc-vs", vs_namespace)
wait_until_all_pods_are_ready(kube_apis.v1, vs_namespace)
print("------------------------- Deploy Secret -----------------------------")
create_secret_from_yaml(kube_apis.v1, test_namespace, src_vs_sec_yaml)
create_secret_from_yaml(kube_apis.v1, vs_namespace, src_vs_sec_yaml)
print(f"------------------------- Deploy policy ---------------------------")
src_pol_name = create_ap_waf_policy_from_yaml(
kube_apis.custom_objects, src_pol_yaml, test_namespace, test_namespace,
kube_apis.custom_objects, src_pol_yaml, vs_namespace, test_namespace,
True, True, ap_pol_name, log_name, f"syslog:server={syslog_ep}:514")
print("------------------------- Deploy Virtual Server -----------------------------------")
vs_name = create_virtual_server_from_yaml(
kube_apis.custom_objects, waf_spec_vs_src, test_namespace)
vs_host = get_first_host_from_yaml(waf_spec_vs_src)
vs_paths = get_paths_from_vs_yaml(waf_spec_vs_src)
return (src_pol_name, vs_name, vs_host, vs_paths)
kube_apis.custom_objects, vs_yaml, vs_namespace)
return (src_pol_name, vs_name)

def cleanup(kube_apis, ingress_controller_prerequisites, src_pol_name,
test_namespace, vs_name) -> None:
test_namespace, vs_or_vsr, vs_name, vsr) -> None:
vsr_namespace = test_namespace if vs_or_vsr == "vs" else vsr.namespace
replace_configmap_from_yaml(
kube_apis.v1,
ingress_controller_prerequisites.config_map['metadata']['name'],
ingress_controller_prerequisites.namespace,
f"{DEPLOYMENTS}/common/nginx-config.yaml")
delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace)
delete_ap_policy(kube_apis.custom_objects, ap_pol_name, test_namespace)
delete_policy(kube_apis.custom_objects, src_pol_name, test_namespace)
delete_common_app(kube_apis, "grpc-vs", test_namespace)
delete_policy(kube_apis.custom_objects, src_pol_name, vsr_namespace)
delete_common_app(kube_apis, "grpc-vs", vsr_namespace)
delete_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace)
delete_virtual_server(kube_apis.custom_objects, vs_name, test_namespace)
delete_items_from_yaml(kube_apis, src_vs_sec_yaml, test_namespace)
if vs_or_vsr == "vs":
delete_virtual_server(kube_apis.custom_objects, vs_name, test_namespace)
delete_items_from_yaml(kube_apis, src_vs_sec_yaml, test_namespace)
elif vs_or_vsr == "vsr":
print("Delete test namespaces")
delete_namespace(kube_apis.v1, vsr.namespace)

def grpc_waf_block(kube_apis, test_namespace, public_ip, vs_host, port_ssl):
syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name

cert = get_certificate(public_ip, vs_host, port_ssl)
target = f'{public_ip}:{port_ssl}'
credentials = grpc.ssl_channel_credentials(root_certificates=cert.encode())
Expand All @@ -145,20 +192,9 @@ def grpc_waf_block(kube_apis, test_namespace, public_ip, vs_host, port_ssl):
except grpc.RpcError as e:
ex = e.details()
print(ex)

log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace)
assert (
invalid_resp_text in ex and
'ASM:attack_type="Directory Indexing"' in log_contents and
'violations="Illegal gRPC method"' in log_contents and
'severity="Error"' in log_contents and
'outcome="REJECTED"' in log_contents
)

assert invalid_resp_text in ex

def grpc_waf_allow(kube_apis, test_namespace, public_ip, vs_host, port_ssl):
syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name

cert = get_certificate(public_ip, vs_host, port_ssl)
target = f'{public_ip}:{port_ssl}'
credentials = grpc.ssl_channel_credentials(root_certificates=cert.encode())
Expand All @@ -173,15 +209,7 @@ def grpc_waf_allow(kube_apis, test_namespace, public_ip, vs_host, port_ssl):
except grpc.RpcError as e:
print(e.details())
pytest.fail("RPC error was not expected during call, exiting...")

log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace)
assert (
valid_resp_txt in response.message and
'ASM:attack_type="N/A"' in log_contents and
'violations="N/A"' in log_contents and
'severity="Informational"' in log_contents and
'outcome="PASSED"' in log_contents
)
assert valid_resp_txt in response.message


@pytest.mark.skip_for_nginx_oss
Expand All @@ -202,8 +230,7 @@ def grpc_waf_allow(kube_apis, test_namespace, public_ip, vs_host, port_ssl):
indirect=True,
)
class TestAppProtectVSGrpc:
@pytest.mark.smoke
@pytest.mark.parametrize("appprotect_setup", [{"policy": "sayhello",}], indirect=True)
@pytest.mark.parametrize("appprotect_setup", [{"policy": "sayhello", "vs_or_vsr": "vs",}], indirect=True)
def test_responses_grpc_block(
self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_ap,
appprotect_setup, test_namespace):
Expand All @@ -216,8 +243,16 @@ def test_responses_grpc_block(
appprotect_setup.public_endpoint.public_ip,
appprotect_setup.vs_host,
appprotect_setup.public_endpoint.port_ssl)
syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name
log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace)
assert (
'ASM:attack_type="Directory Indexing"' in log_contents and
'violations="Illegal gRPC method"' in log_contents and
'severity="Error"' in log_contents and
'outcome="REJECTED"' in log_contents
)

@pytest.mark.parametrize("appprotect_setup", [{"policy": "saygoodbye", }], indirect=True)
@pytest.mark.parametrize("appprotect_setup", [{"policy": "saygoodbye", "vs_or_vsr": "vs",}], indirect=True)
def test_responses_grpc_allow(
self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_ap,
appprotect_setup, test_namespace
Expand All @@ -231,3 +266,60 @@ def test_responses_grpc_allow(
appprotect_setup.public_endpoint.public_ip,
appprotect_setup.vs_host,
appprotect_setup.public_endpoint.port_ssl)
syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name
log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace)
assert (
'ASM:attack_type="N/A"' in log_contents and
'violations="N/A"' in log_contents and
'severity="Informational"' in log_contents and
'outcome="PASSED"' in log_contents
)


@pytest.mark.skip_for_nginx_oss
@pytest.mark.appprotect
@pytest.mark.parametrize(
"crd_ingress_controller_with_ap",
[
{
"type": "complete",
"extra_args": [
f"-enable-custom-resources",
f"-enable-leader-election=false",
f"-enable-app-protect",
f"-enable-preview-policies",
],
},
],
indirect=True,
)
class TestAppProtectVSRGrpc:
@pytest.mark.parametrize("appprotect_setup", [{"policy": "sayhello", "vs_or_vsr": "vsr",}], indirect=True)
def test_responses_grpc_block(
self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_ap,
appprotect_setup, test_namespace
):
"""
Test grpc-block-hello AppProtect policy: Blocks /sayhello gRPC method only
Client sends request to /sayhello
"""
grpc_waf_block(kube_apis,
appprotect_setup.namespace,
appprotect_setup.public_endpoint.public_ip,
appprotect_setup.vs_host,
appprotect_setup.public_endpoint.port_ssl)

@pytest.mark.parametrize("appprotect_setup", [{"policy": "saygoodbye", "vs_or_vsr": "vsr"}], indirect=True)
def test_responses_grpc_allow(
self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_ap,
appprotect_setup, test_namespace
):
"""
Test grpc-block-goodbye AppProtect policy: Blocks /saygoodbye gRPC method only
Client sends request to /sayhello thus should pass
"""
grpc_waf_allow(kube_apis,
appprotect_setup.namespace,
appprotect_setup.public_endpoint.public_ip,
appprotect_setup.vs_host,
appprotect_setup.public_endpoint.port_ssl)

0 comments on commit ac14863

Please sign in to comment.