-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AppProtect gRPC automation tests #1603
Changes from 3 commits
fae2ce5
7c3f987
716e63f
0632d63
2ca12fb
932125e
fe7042c
4e82410
9bb67d0
5ddc541
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
apiVersion: appprotect.f5.com/v1beta1 | ||
kind: APPolicy | ||
metadata: | ||
name: grpc-block-saygoodbye | ||
spec: | ||
policy: | ||
blocking-settings: | ||
violations: | ||
- name: VIOL_GRPC_METHOD | ||
block: true | ||
alarm: true | ||
applicationLanguage: utf-8 | ||
bot-defense: | ||
settings: | ||
isEnabled: false | ||
grpc-profiles: | ||
- associateUrls: true | ||
defenseAttributes: | ||
allowUnknownFields: false | ||
maximumDataLength: "10000" | ||
description: My first profile | ||
idlFiles: | ||
- idlFile: | ||
fileName: autheid.proto | ||
name: gProf1 | ||
idl-files: | ||
- isBase64: true | ||
fileName: autheid.proto | ||
contents: Ly8gVGhlIGdyZWV0aW5nIHNlcnZpY2UgZGVmaW5pdGlvbi4KCnN5bnRheCA9ICJwcm90bzMiOwoKcGFja2FnZSBoZWxsb3dvcmxkOwoKc2VydmljZSBHcmVldGVyIHsKICAvLyBTZW5kcyBhIGdyZWV0aW5nCiAgcnBjIFNheUhlbGxvIChIZWxsb1JlcXVlc3QpIHJldHVybnMgKEhlbGxvUmVwbHkpIHt9Cn0KCi8vIFRoZSByZXF1ZXN0IG1lc3NhZ2UgY29udGFpbmluZyB0aGUgdXNlcidzIG5hbWUuCm1lc3NhZ2UgSGVsbG9SZXF1ZXN0IHsKICBzdHJpbmcgbmFtZSA9IDE7Cn0KCi8vIFRoZSByZXNwb25zZSBtZXNzYWdlIGNvbnRhaW5pbmcgdGhlIGdyZWV0aW5ncwptZXNzYWdlIEhlbGxvUmVwbHkgewogIHN0cmluZyBtZXNzYWdlID0gMTsKfQo= | ||
name: valid_string_encoding_policy | ||
template: | ||
name: POLICY_TEMPLATE_NGINX_BASE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
apiVersion: appprotect.f5.com/v1beta1 | ||
kind: APPolicy | ||
metadata: | ||
name: grpc-block-sayhello | ||
spec: | ||
policy: | ||
blocking-settings: | ||
violations: | ||
- name: VIOL_GRPC_METHOD | ||
block: True | ||
alarm: True | ||
applicationLanguage: utf-8 | ||
bot-defense: | ||
settings: | ||
isEnabled: false | ||
grpc-profiles: | ||
- associateUrls: true | ||
defenseAttributes: | ||
allowUnknownFields: false | ||
maximumDataLength: "10000" | ||
description: My first profile | ||
idlFiles: | ||
- idlFile: | ||
fileName: autheid.proto | ||
name: gProf1 | ||
idl-files: | ||
- isBase64: true | ||
fileName: autheid.proto | ||
contents: Ly8gVGhlIGdyZWV0aW5nIHNlcnZpY2UgZGVmaW5pdGlvbi4KCnN5bnRheCA9ICJwcm90bzMiOwoKcGFja2FnZSBoZWxsb3dvcmxkOwoKc2VydmljZSBHcmVldGVyIHsKICAvLyBTZW5kcyBhIGdyZWV0aW5nCiAgcnBjIFNheUdvb2RieWUgKEhlbGxvUmVxdWVzdCkgcmV0dXJucyAoSGVsbG9SZXBseSkge30KfQoKLy8gVGhlIHJlcXVlc3QgbWVzc2FnZSBjb250YWluaW5nIHRoZSB1c2VyJ3MgbmFtZS4KbWVzc2FnZSBIZWxsb1JlcXVlc3QgewogIHN0cmluZyBuYW1lID0gMTsKfQoKLy8gVGhlIHJlc3BvbnNlIG1lc3NhZ2UgY29udGFpbmluZyB0aGUgZ3JlZXRpbmdzCm1lc3NhZ2UgSGVsbG9SZXBseSB7CiAgc3RyaW5nIG1lc3NhZ2UgPSAxOwp9Cg== | ||
name: valid_string_encoding_policy | ||
template: | ||
name: POLICY_TEMPLATE_NGINX_BASE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
apiVersion: extensions/v1beta1 | ||
kind: Ingress | ||
metadata: | ||
name: appprotect-ingress | ||
annotations: | ||
nginx.org/grpc-services: "grpc-svc" | ||
kubernetes.io/ingress.class: "nginx" | ||
appprotect.f5.com/app-protect-policy: "default/grpc" | ||
appprotect.f5.com/app-protect-enable: "True" | ||
appprotect.f5.com/app-protect-security-log-enable: "True" | ||
appprotect.f5.com/app-protect-security-log: "default/logconf" | ||
appprotect.f5.com/app-protect-security-log-destination: "syslog:server=172.17.0.10:514" | ||
spec: | ||
tls: | ||
- hosts: | ||
- appprotect.example.com | ||
secretName: appprotect-secret | ||
rules: | ||
- host: appprotect.example.com | ||
http: | ||
paths: | ||
- path: /helloworld.Greeter | ||
backend: | ||
serviceName: grpc-svc | ||
servicePort: 50051 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
kind: ConfigMap | ||
apiVersion: v1 | ||
metadata: | ||
name: nginx-config | ||
namespace: nginx-ingress | ||
data: | ||
http2: "True" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: grpc | ||
spec: | ||
replicas: 1 | ||
selector: | ||
matchLabels: | ||
app: greeter | ||
template: | ||
metadata: | ||
labels: | ||
app: greeter | ||
spec: | ||
containers: | ||
- name: greeter | ||
image: nginxkic/test-grpc-server:0.1 | ||
ports: | ||
- containerPort: 50051 | ||
--- | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: grpc-svc | ||
spec: | ||
ports: | ||
- port: 50051 | ||
targetPort: 50051 | ||
protocol: TCP | ||
name: grpc | ||
selector: | ||
app: greeter |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import subprocess | ||
import pytest | ||
from python_hosts import Hosts, HostsEntry | ||
from settings import TEST_DATA, DEPLOYMENTS | ||
from suite.custom_resources_utils import ( | ||
create_ap_logconf_from_yaml, | ||
create_ap_policy_from_yaml, | ||
delete_ap_policy, | ||
delete_ap_logconf, | ||
) | ||
from suite.resources_utils import ( | ||
wait_before_test, | ||
create_example_app, | ||
wait_until_all_pods_are_ready, | ||
create_items_from_yaml, | ||
delete_items_from_yaml, | ||
delete_common_app, | ||
replace_configmap_from_yaml, | ||
create_ingress_with_ap_annotations, | ||
wait_before_test, | ||
get_file_contents, | ||
) | ||
from suite.yaml_utils import get_first_ingress_host_from_yaml | ||
|
||
my_hosts = Hosts() | ||
vepatel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
log_loc = f"/var/log/messages" | ||
valid_resp_txt = "Hello" | ||
invalid_resp_text = "The request was rejected. Please consult with your administrator." | ||
|
||
class BackendSetup: | ||
""" | ||
Encapsulate the example details. | ||
|
||
Attributes: | ||
ingress_host (str): | ||
""" | ||
|
||
def __init__(self, ingress_host, ssl_port): | ||
self.ingress_host = ingress_host | ||
self.ssl_port = ssl_port | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def backend_setup(request, kube_apis, ingress_controller_endpoint, ingress_controller_prerequisites, test_namespace) -> BackendSetup: | ||
""" | ||
Deploy a simple application and AppProtect manifests. | ||
|
||
:param request: pytest fixture | ||
:param kube_apis: client apis | ||
:param ingress_controller_endpoint: public endpoint | ||
:param test_namespace: | ||
:return: BackendSetup | ||
""" | ||
print("------------------------- Replace ConfigMap with HTTP2 -------------------------") | ||
replace_configmap_from_yaml(kube_apis.v1, | ||
ingress_controller_prerequisites.config_map['metadata']['name'], | ||
ingress_controller_prerequisites.namespace, | ||
f"{TEST_DATA}/appprotect/grpc/nginx-config.yaml") | ||
|
||
policy = request.param["policy"] | ||
print("------------------------- Deploy backend application -------------------------") | ||
create_example_app(kube_apis, "grpc", test_namespace) | ||
wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) | ||
|
||
print("------------------------- Deploy Secret -----------------------------") | ||
src_sec_yaml = f"{TEST_DATA}/appprotect/appprotect-secret.yaml" | ||
create_items_from_yaml(kube_apis, src_sec_yaml, test_namespace) | ||
|
||
print("------------------------- Deploy logconf -----------------------------") | ||
src_log_yaml = f"{TEST_DATA}/appprotect/logconf.yaml" | ||
log_name = create_ap_logconf_from_yaml(kube_apis.custom_objects, src_log_yaml, test_namespace) | ||
|
||
print(f"------------------------- Deploy appolicy: {policy} ---------------------------") | ||
src_pol_yaml = f"{TEST_DATA}/appprotect/grpc/{policy}.yaml" | ||
pol_name = create_ap_policy_from_yaml(kube_apis.custom_objects, src_pol_yaml, test_namespace) | ||
|
||
print("------------------------- Deploy Syslog -----------------------------") | ||
src_syslog_yaml = f"{TEST_DATA}/appprotect/syslog.yaml" | ||
create_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace) | ||
wait_until_all_pods_are_ready(kube_apis.v1, test_namespace) | ||
wait_before_test(10) | ||
syslog_ep = ( | ||
kube_apis.v1.read_namespaced_endpoints("syslog-svc", test_namespace) | ||
.subsets[0] | ||
.addresses[0] | ||
.ip | ||
) | ||
print(syslog_ep) | ||
print("------------------------- Deploy ingress -----------------------------") | ||
ingress_host = {} | ||
src_ing_yaml = f"{TEST_DATA}/appprotect/grpc/ingress.yaml" | ||
create_ingress_with_ap_annotations(kube_apis, src_ing_yaml, test_namespace, policy, "True", "True", f"{syslog_ep}:514") | ||
ingress_host = get_first_ingress_host_from_yaml(src_ing_yaml) | ||
node_ip = request.config.getoption("--node-ip", None) | ||
new_entry = HostsEntry(entry_type='ipv4', address=node_ip, names=[ingress_host]) | ||
print(new_entry) | ||
my_hosts.add([new_entry]) | ||
wait_before_test(40) | ||
vepatel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def fin(): | ||
print("Clean up:") | ||
delete_items_from_yaml(kube_apis, src_syslog_yaml, test_namespace) | ||
delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) | ||
delete_ap_policy(kube_apis.custom_objects, pol_name, test_namespace) | ||
delete_ap_logconf(kube_apis.custom_objects, log_name, test_namespace) | ||
delete_common_app(kube_apis, "grpc", test_namespace) | ||
delete_items_from_yaml(kube_apis, src_sec_yaml, test_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") | ||
|
||
request.addfinalizer(fin) | ||
|
||
return BackendSetup(ingress_host, ingress_controller_endpoint.port_ssl) | ||
|
||
|
||
@pytest.mark.skip_for_nginx_oss | ||
@pytest.mark.appprotect | ||
@pytest.mark.smoke | ||
@pytest.mark.parametrize( | ||
"crd_ingress_controller_with_ap", | ||
[{"extra_args": [f"-enable-custom-resources", f"-enable-app-protect"]}], | ||
indirect=["crd_ingress_controller_with_ap"], | ||
) | ||
class TestAppProtect: | ||
@pytest.mark.parametrize("backend_setup", [{"policy": "grpc-block-sayhello"}], indirect=True) | ||
vepatel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def test_responses_grpc_block( | ||
self, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace | ||
): | ||
""" | ||
Test grpc-block-hello AppProtect policy: Blocks /sayhello gRPC method only | ||
Client sends request to /sayhello | ||
""" | ||
syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name | ||
block_response = subprocess.run(["binaries/./grpc_client", "-address", f"{backend_setup.ingress_host}:{backend_setup.ssl_port}"], capture_output=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it make sense to use https://github.com/grpc/grpc/blob/v1.37.1/examples/python/helloworld/greeter_client.py ? (providing we can make it work). this way there will be no need to add any binaries to the repo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm checking https://pypi.org/project/grpc-requests/ which is based on the same, will update if it works! |
||
stdout = (block_response.stderr).decode("ascii") | ||
print(stdout) | ||
log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) | ||
print(log_contents) | ||
assert ( | ||
valid_resp_txt not in stdout and | ||
invalid_resp_text in stdout 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 | ||
) | ||
|
||
@pytest.mark.parametrize("backend_setup", [{"policy": "grpc-block-saygoodbye"}], indirect=True) | ||
def test_responses_grpc_allow( | ||
self, kube_apis, crd_ingress_controller_with_ap, backend_setup, test_namespace | ||
): | ||
""" | ||
Test grpc-block-saygoodbye AppProtect policy: Blocks /saygoodbye gRPC method only | ||
Client sends request to /sayhello | ||
""" | ||
syslog_pod = kube_apis.v1.list_namespaced_pod(test_namespace).items[-1].metadata.name | ||
allow_response = subprocess.run(["binaries/./grpc_client", "-address", f"{backend_setup.ingress_host}:{backend_setup.ssl_port}"], capture_output=True) | ||
stdout = (allow_response.stderr).decode("ascii") | ||
print(stdout) | ||
log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, test_namespace) | ||
print(log_contents) | ||
assert ( | ||
valid_resp_txt in stdout and | ||
invalid_resp_text not in stdout 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 | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we publish the sources similarly to how we published the sources for the TCP and UDP servers? so we don't loose it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too sure about publishing sources for this one as the code isn't written by our team members unlike TCP/UDP server one which @soneillf5 wrote. Also If we're to add source in the tests, i'd prefer adding them to
examples/
first.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the case of the backend, it's just a Dockerfile, right? we don't really modify the backend code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where does the test-grpc-server come from then ?