Skip to content

Commit b078841

Browse files
authored
add support for readonlyrootfilesystem test for wafv5 (#6708)
1 parent 41d4920 commit b078841

File tree

3 files changed

+294
-42
lines changed

3 files changed

+294
-42
lines changed

tests/suite/fixtures/ic_fixtures.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,25 @@ def crd_ingress_controller_with_waf_v5(
289289
ap_uds_crd_name,
290290
f"{CRDS}/appprotect.f5.com_apusersigs.yaml",
291291
)
292-
name = create_ingress_controller_wafv5(
293-
kube_apis.v1,
294-
kube_apis.apps_v1_api,
295-
cli_arguments,
296-
namespace,
297-
"regcred",
298-
request.param.get("extra_args", None),
299-
)
292+
if request.param["type"] == "rorfs": # WAFv5 with readOnlyRootFileSystem
293+
name = create_ingress_controller_wafv5(
294+
kube_apis.v1,
295+
kube_apis.apps_v1_api,
296+
cli_arguments,
297+
namespace,
298+
"regcred",
299+
request.param.get("extra_args", None),
300+
True,
301+
)
302+
else:
303+
name = create_ingress_controller_wafv5(
304+
kube_apis.v1,
305+
kube_apis.apps_v1_api,
306+
cli_arguments,
307+
namespace,
308+
"regcred",
309+
request.param.get("extra_args", None),
310+
)
300311
try:
301312
with open(f"{dir}/wafv5.tgz", "rb") as f:
302313
file_content = f.read()
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import pytest
2+
import requests
3+
from settings import TEST_DATA
4+
from suite.utils.policy_resources_utils import create_policy_from_yaml, delete_policy
5+
from suite.utils.resources_utils import wait_before_test
6+
from suite.utils.vs_vsr_resources_utils import (
7+
create_virtual_server_from_yaml,
8+
delete_virtual_server,
9+
patch_v_s_route_from_yaml,
10+
patch_virtual_server_from_yaml,
11+
)
12+
13+
14+
@pytest.fixture(scope="class")
15+
def waf_setup(kube_apis, test_namespace) -> None:
16+
waf = f"{TEST_DATA}/ap-waf-v5/policies/waf.yaml"
17+
create_policy_from_yaml(kube_apis.custom_objects, waf, test_namespace)
18+
wait_before_test()
19+
20+
21+
@pytest.mark.skip_for_nginx_oss
22+
@pytest.mark.appprotect_waf_v5
23+
@pytest.mark.parametrize(
24+
"crd_ingress_controller_with_waf_v5, virtual_server_setup",
25+
[
26+
(
27+
{
28+
"type": "rorfs",
29+
"extra_args": [
30+
f"-enable-app-protect",
31+
],
32+
},
33+
{
34+
"example": "ap-waf-v5",
35+
"app_type": "simple",
36+
},
37+
),
38+
],
39+
indirect=True,
40+
)
41+
class TestAppProtectWAFv5IntegrationVSrorfs:
42+
def restore_default_vs(self, kube_apis, virtual_server_setup) -> None:
43+
"""
44+
Restore VirtualServer without policy spec
45+
"""
46+
std_vs_src = f"{TEST_DATA}/ap-waf-v5/standard/virtual-server.yaml"
47+
delete_virtual_server(kube_apis.custom_objects, virtual_server_setup.vs_name, virtual_server_setup.namespace)
48+
create_virtual_server_from_yaml(kube_apis.custom_objects, std_vs_src, virtual_server_setup.namespace)
49+
wait_before_test()
50+
51+
@pytest.mark.parametrize(
52+
"vs_src",
53+
[f"{TEST_DATA}/ap-waf-v5/virtual-server-waf-spec.yaml"],
54+
)
55+
def test_ap_waf_v5_policy_block_vs(
56+
self,
57+
kube_apis,
58+
ingress_controller_prerequisites,
59+
crd_ingress_controller_with_waf_v5,
60+
test_namespace,
61+
virtual_server_setup,
62+
waf_setup,
63+
vs_src,
64+
):
65+
patch_virtual_server_from_yaml(
66+
kube_apis.custom_objects,
67+
virtual_server_setup.vs_name,
68+
vs_src,
69+
virtual_server_setup.namespace,
70+
)
71+
72+
print("----------------------- Send request with embedded malicious script----------------------")
73+
count = 0
74+
response = requests.get(
75+
virtual_server_setup.backend_1_url + "</script>",
76+
headers={"host": virtual_server_setup.vs_host},
77+
)
78+
while count < 5 and "Request Rejected" not in response.text:
79+
response = requests.get(
80+
virtual_server_setup.backend_1_url + "</script>",
81+
headers={"host": virtual_server_setup.vs_host},
82+
)
83+
wait_before_test()
84+
count += 1
85+
self.restore_default_vs(kube_apis, virtual_server_setup)
86+
assert response.status_code == 200
87+
assert "The requested URL was rejected. Please consult with your administrator." in response.text
88+
89+
90+
@pytest.mark.skip_for_nginx_oss
91+
@pytest.mark.appprotect_waf_v5
92+
@pytest.mark.parametrize(
93+
"crd_ingress_controller_with_waf_v5, v_s_route_setup",
94+
[
95+
(
96+
{
97+
"type": "rorfs",
98+
"extra_args": [
99+
f"-enable-app-protect",
100+
],
101+
},
102+
{
103+
"example": "virtual-server-route",
104+
},
105+
)
106+
],
107+
indirect=True,
108+
)
109+
class TestAppProtectWAFv5IntegrationVSRrorfs:
110+
111+
def restore_default_vsr(self, kube_apis, v_s_route_setup) -> None:
112+
"""
113+
Function to revert vsr deployments to standard state
114+
"""
115+
patch_src_m = f"{TEST_DATA}/virtual-server-route/route-multiple.yaml"
116+
patch_v_s_route_from_yaml(
117+
kube_apis.custom_objects,
118+
v_s_route_setup.route_m.name,
119+
patch_src_m,
120+
v_s_route_setup.route_m.namespace,
121+
)
122+
wait_before_test()
123+
124+
def test_ap_waf_v5_policy_block_vsr(
125+
self,
126+
kube_apis,
127+
ingress_controller_prerequisites,
128+
crd_ingress_controller_with_waf_v5,
129+
test_namespace,
130+
v_s_route_setup,
131+
):
132+
req_url = f"http://{v_s_route_setup.public_endpoint.public_ip}:{v_s_route_setup.public_endpoint.port}"
133+
waf_subroute_vsr_src = f"{TEST_DATA}/ap-waf-v5/virtual-server-route-waf-subroute.yaml"
134+
pol = create_policy_from_yaml(
135+
kube_apis.custom_objects,
136+
f"{TEST_DATA}/ap-waf-v5/policies/waf.yaml",
137+
v_s_route_setup.route_m.namespace,
138+
)
139+
wait_before_test()
140+
patch_v_s_route_from_yaml(
141+
kube_apis.custom_objects,
142+
v_s_route_setup.route_m.name,
143+
waf_subroute_vsr_src,
144+
v_s_route_setup.route_m.namespace,
145+
)
146+
wait_before_test()
147+
print("----------------------- Send request with embedded malicious script----------------------")
148+
count = 0
149+
response = requests.get(
150+
f'{req_url}{v_s_route_setup.route_m.paths[0]}+"</script>"',
151+
headers={"host": v_s_route_setup.vs_host},
152+
)
153+
while count < 5 and "Request Rejected" not in response.text:
154+
response = requests.get(
155+
f'{req_url}{v_s_route_setup.route_m.paths[0]}+"</script>"',
156+
headers={"host": v_s_route_setup.vs_host},
157+
)
158+
wait_before_test()
159+
count += 1
160+
self.restore_default_vsr(kube_apis, v_s_route_setup)
161+
delete_policy(kube_apis.custom_objects, pol, v_s_route_setup.route_m.namespace)
162+
assert response.status_code == 200
163+
assert "The requested URL was rejected. Please consult with your administrator." in response.text

tests/suite/utils/resources_utils.py

Lines changed: 112 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,7 +1206,7 @@ def create_ingress_controller(v1: CoreV1Api, apps_v1_api: AppsV1Api, cli_argumen
12061206

12071207

12081208
def create_ingress_controller_wafv5(
1209-
v1: CoreV1Api, apps_v1_api: AppsV1Api, cli_arguments, namespace, reg_secret, args=None
1209+
v1: CoreV1Api, apps_v1_api: AppsV1Api, cli_arguments, namespace, reg_secret, args=None, rorfs=False
12101210
) -> str:
12111211
"""
12121212
Create an Ingress Controller according to the params.
@@ -1225,6 +1225,9 @@ def create_ingress_controller_wafv5(
12251225
dep["spec"]["replicas"] = int(cli_arguments["replicas"])
12261226
dep["spec"]["template"]["spec"]["containers"][0]["image"] = cli_arguments["image"]
12271227
dep["spec"]["template"]["spec"]["containers"][0]["imagePullPolicy"] = cli_arguments["image-pull-policy"]
1228+
if "readOnlyRootFilesystem" not in dep["spec"]["template"]["spec"]["containers"][0]["securityContext"]:
1229+
dep["spec"]["template"]["spec"]["containers"][0]["securityContext"]["readOnlyRootFilesystem"] = rorfs
1230+
12281231
template_spec = dep["spec"]["template"]["spec"]
12291232
if "imagePullSecrets" not in template_spec:
12301233
template_spec["imagePullSecrets"] = []
@@ -1233,43 +1236,109 @@ def create_ingress_controller_wafv5(
12331236
if "volumes" not in template_spec:
12341237
template_spec["volumes"] = []
12351238

1236-
template_spec["volumes"].extend(
1237-
[
1238-
{
1239-
"name": "app-protect-bd-config",
1240-
"emptyDir": {},
1241-
},
1242-
{
1243-
"name": "app-protect-config",
1244-
"emptyDir": {},
1245-
},
1246-
{
1247-
"name": "app-protect-bundles",
1248-
"emptyDir": {},
1249-
},
1250-
]
1251-
)
1239+
if rorfs and "initContainers" not in template_spec:
1240+
template_spec["initContainers"] = []
1241+
template_spec["initContainers"].extend(
1242+
[
1243+
{
1244+
"name": "init-nginx-ingress",
1245+
"image": cli_arguments["image"],
1246+
"imagePullPolicy": "IfNotPresent",
1247+
"command": ["cp", "-vdR", "/etc/nginx/.", "/mnt/etc"],
1248+
"securityContext": {
1249+
"allowPrivilegeEscalation": False,
1250+
"readOnlyRootFilesystem": True,
1251+
"runAsUser": 101, # nginx
1252+
"runAsNonRoot": True,
1253+
"capabilities": {"drop": ["ALL"]},
1254+
},
1255+
"volumeMounts": [{"mountPath": "/mnt/etc", "name": "nginx-etc"}],
1256+
}
1257+
]
1258+
)
1259+
1260+
if rorfs:
1261+
template_spec["volumes"].extend(
1262+
[
1263+
{
1264+
"name": "app-protect-bd-config",
1265+
"emptyDir": {},
1266+
},
1267+
{
1268+
"name": "app-protect-config",
1269+
"emptyDir": {},
1270+
},
1271+
{
1272+
"name": "app-protect-bundles",
1273+
"emptyDir": {},
1274+
},
1275+
{"name": "nginx-etc", "emptyDir": {}},
1276+
{"name": "nginx-log", "emptyDir": {}},
1277+
{"name": "nginx-cache", "emptyDir": {}},
1278+
{"name": "nginx-lib", "emptyDir": {}},
1279+
]
1280+
)
1281+
else:
1282+
template_spec["volumes"].extend(
1283+
[
1284+
{
1285+
"name": "app-protect-bd-config",
1286+
"emptyDir": {},
1287+
},
1288+
{
1289+
"name": "app-protect-config",
1290+
"emptyDir": {},
1291+
},
1292+
{
1293+
"name": "app-protect-bundles",
1294+
"emptyDir": {},
1295+
},
1296+
]
1297+
)
12521298

12531299
container = dep["spec"]["template"]["spec"]["containers"][0]
12541300
if "volumeMounts" not in container:
12551301
container["volumeMounts"] = []
12561302

1257-
container["volumeMounts"].extend(
1258-
[
1259-
{
1260-
"name": "app-protect-bd-config",
1261-
"mountPath": "/opt/app_protect/bd_config",
1262-
},
1263-
{
1264-
"name": "app-protect-config",
1265-
"mountPath": "/opt/app_protect/config",
1266-
},
1267-
{
1268-
"name": "app-protect-bundles",
1269-
"mountPath": "/etc/app_protect/bundles",
1270-
},
1271-
]
1272-
)
1303+
if rorfs:
1304+
container["volumeMounts"].extend(
1305+
[
1306+
{
1307+
"name": "app-protect-bd-config",
1308+
"mountPath": "/opt/app_protect/bd_config",
1309+
},
1310+
{
1311+
"name": "app-protect-config",
1312+
"mountPath": "/opt/app_protect/config",
1313+
},
1314+
{
1315+
"name": "app-protect-bundles",
1316+
"mountPath": "/etc/app_protect/bundles",
1317+
},
1318+
{"name": "nginx-etc", "mountPath": "/etc/nginx"},
1319+
{"name": "nginx-log", "mountPath": "/var/log/nginx"},
1320+
{"name": "nginx-cache", "mountPath": "/var/cache/nginx"},
1321+
{"name": "nginx-lib", "mountPath": "/var/lib/nginx"},
1322+
]
1323+
)
1324+
else:
1325+
container["volumeMounts"].extend(
1326+
[
1327+
{
1328+
"name": "app-protect-bd-config",
1329+
"mountPath": "/opt/app_protect/bd_config",
1330+
},
1331+
{
1332+
"name": "app-protect-config",
1333+
"mountPath": "/opt/app_protect/config",
1334+
},
1335+
{
1336+
"name": "app-protect-bundles",
1337+
"mountPath": "/etc/app_protect/bundles",
1338+
},
1339+
]
1340+
)
1341+
12731342
dep["spec"]["template"]["spec"]["containers"][0]["args"].extend(
12741343
[
12751344
f"-default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret",
@@ -1281,7 +1350,11 @@ def create_ingress_controller_wafv5(
12811350
"name": "waf-config-mgr",
12821351
"image": f"{NGX_REG}/nap/waf-config-mgr:{WAF_V5_VERSION}",
12831352
"imagePullPolicy": "IfNotPresent",
1284-
"securityContext": {"allowPrivilegeEscalation": False, "capabilities": {"drop": ["all"]}},
1353+
"securityContext": {
1354+
"allowPrivilegeEscalation": False,
1355+
"capabilities": {"drop": ["all"]},
1356+
"readOnlyRootFilesystem": rorfs,
1357+
},
12851358
"volumeMounts": [
12861359
{
12871360
"name": "app-protect-bd-config",
@@ -1301,6 +1374,11 @@ def create_ingress_controller_wafv5(
13011374
"name": "waf-enforcer",
13021375
"image": f"{NGX_REG}/nap/waf-enforcer:{WAF_V5_VERSION}",
13031376
"imagePullPolicy": "IfNotPresent",
1377+
"securityContext": {
1378+
"allowPrivilegeEscalation": False,
1379+
"capabilities": {"drop": ["all"]},
1380+
"readOnlyRootFilesystem": rorfs,
1381+
},
13041382
"env": [{"name": "ENFORCER_PORT", "value": "50000"}],
13051383
"volumeMounts": [
13061384
{

0 commit comments

Comments
 (0)