diff --git a/build/scripts/nap-dos.sh b/build/scripts/nap-dos.sh index e07a759b84..3e906d26c1 100755 --- a/build/scripts/nap-dos.sh +++ b/build/scripts/nap-dos.sh @@ -2,5 +2,5 @@ set -e -mkdir -p /root/app_protect_dos /etc/nginx/dos/policies /etc/nginx/dos/logconfs /shared/cores /var/log/adm /var/run/adm +mkdir -p /root/app_protect_dos /etc/nginx/dos/policies /etc/nginx/dos/logconfs /etc/nginx/dos/allowlist /shared/cores /var/log/adm /var/run/adm chmod 777 /shared/cores /var/log/adm /var/run/adm /etc/app_protect_dos diff --git a/config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml b/config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml index b7019df408..831ee0b626 100644 --- a/config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml +++ b/config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml @@ -42,6 +42,16 @@ spec: description: DosProtectedResourceSpec defines the properties and values a DosProtectedResource can have. properties: + allowList: + description: AllowList is a list of allowed IPs and subnet masks + items: + description: AllowListEntry represents an IP address and a subnet + mask. + properties: + ipWithMask: + type: string + type: object + type: array apDosMonitor: description: 'ApDosMonitor is how NGINX App Protect DoS monitors the stress level of the protected object. The monitor requests are sent diff --git a/deploy/crds-nap-dos.yaml b/deploy/crds-nap-dos.yaml index e7faf33730..9e45aa1e87 100644 --- a/deploy/crds-nap-dos.yaml +++ b/deploy/crds-nap-dos.yaml @@ -187,6 +187,16 @@ spec: description: DosProtectedResourceSpec defines the properties and values a DosProtectedResource can have. properties: + allowList: + description: AllowList is a list of allowed IPs and subnet masks + items: + description: AllowListEntry represents an IP address and a subnet + mask. + properties: + ipWithMask: + type: string + type: object + type: array apDosMonitor: description: 'ApDosMonitor is how NGINX App Protect DoS monitors the stress level of the protected object. The monitor requests are sent diff --git a/docs/content/installation/integrations/app-protect-dos/dos-protected.md b/docs/content/installation/integrations/app-protect-dos/dos-protected.md index fcc42aff2d..105aa38814 100644 --- a/docs/content/installation/integrations/app-protect-dos/dos-protected.md +++ b/docs/content/installation/integrations/app-protect-dos/dos-protected.md @@ -40,6 +40,7 @@ spec: |``dosSecurityLog.enable`` | Enables security log. | ``bool`` | No | |``dosSecurityLog.apDosLogConf`` | The [App Protect DoS log conf]({{< relref "installation/integrations/app-protect-dos/configuration.md#app-protect-dos-logs" >}}) resource. Accepts an optional namespace. | ``string`` | No | |``dosSecurityLog.dosLogDest`` | The log destination for the security log. Accepted variables are ``syslog:server=:``, ``stderr``, ````. Default is ``"syslog:server=127.0.0.1:514"``. | ``string`` | No | +|``allowList`` | List of allowed IP addresses and subnet masks. Each entry is represented by an `IPWithMask` string. | ``[]AllowListEntry`` | No | {{% /table %}} ### DosProtectedResource.apDosPolicy diff --git a/examples/custom-resources/app-protect-dos/apdos-protected.yaml b/examples/custom-resources/app-protect-dos/apdos-protected.yaml index 6ed7b71752..71fbd7b284 100644 --- a/examples/custom-resources/app-protect-dos/apdos-protected.yaml +++ b/examples/custom-resources/app-protect-dos/apdos-protected.yaml @@ -15,3 +15,8 @@ spec: enable: true apDosLogConf: "doslogconf" dosLogDest: "syslog-svc.default.svc.cluster.local:514" + allowList: + - ipWithMask: "192.168.1.1/24" + - ipWithMask: "10.244.0.1/32" + - ipWithMask: "2023::4ef3/128" + - ipWithMask: "2034::2300/120" diff --git a/examples/ingress-resources/app-protect-dos/apdos-protected.yaml b/examples/ingress-resources/app-protect-dos/apdos-protected.yaml index 6ed7b71752..71fbd7b284 100644 --- a/examples/ingress-resources/app-protect-dos/apdos-protected.yaml +++ b/examples/ingress-resources/app-protect-dos/apdos-protected.yaml @@ -15,3 +15,8 @@ spec: enable: true apDosLogConf: "doslogconf" dosLogDest: "syslog-svc.default.svc.cluster.local:514" + allowList: + - ipWithMask: "192.168.1.1/24" + - ipWithMask: "10.244.0.1/32" + - ipWithMask: "2023::4ef3/128" + - ipWithMask: "2034::2300/120" diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go index 71f23e8897..6bd2d696f2 100644 --- a/internal/configs/configurator.go +++ b/internal/configs/configurator.go @@ -37,6 +37,7 @@ const ( appProtectUserSigIndex = "/etc/nginx/waf/nac-usersigs/index.conf" appProtectDosPolicyFolder = "/etc/nginx/dos/policies/" appProtectDosLogConfFolder = "/etc/nginx/dos/logconfs/" + appProtectDosAllowListFolder = "/etc/nginx/dos/allowlist/" ) // DefaultServerSecretPath is the full path to the Secret with a TLS cert and a key for the default server. #nosec G101 @@ -1679,6 +1680,11 @@ func (cnf *Configurator) updateApResources(ingEx *IngressEx) *AppProtectResource func (cnf *Configurator) updateDosResource(dosEx *DosEx) { if dosEx != nil { + if dosEx.DosProtected != nil { + allowListFileName := appProtectDosAllowListFileName(dosEx.DosProtected.GetNamespace(), dosEx.DosProtected.GetName()) + allowListContent := generateApDosAllowListFileContent(dosEx.DosProtected.Spec.AllowList) + cnf.nginxManager.CreateAppProtectResourceFile(allowListFileName, allowListContent) + } if dosEx.DosPolicy != nil { policyFileName := appProtectDosPolicyFileName(dosEx.DosPolicy.GetNamespace(), dosEx.DosPolicy.GetName()) policyContent := generateApResourceFileContent(dosEx.DosPolicy) @@ -1738,6 +1744,48 @@ func generateApResourceFileContent(apResource *unstructured.Unstructured) []byte return data } +func generateApDosAllowListFileContent(allowList []v1beta1.AllowListEntry) []byte { + type IPAddress struct { + IPAddress string `json:"ipAddress"` + } + + type IPAddressList struct { + IPAddresses []IPAddress `json:"ipAddresses"` + BlockRequests string `json:"blockRequests"` + } + + type Policy struct { + IPAddressLists []IPAddressList `json:"ip-address-lists"` + } + + type AllowListPolicy struct { + Policy Policy `json:"policy"` + } + + ipAddresses := make([]IPAddress, len(allowList)) + for i, entry := range allowList { + ipAddresses[i] = IPAddress{IPAddress: entry.IPWithMask} + } + + allowListPolicy := AllowListPolicy{ + Policy: Policy{ + IPAddressLists: []IPAddressList{ + { + IPAddresses: ipAddresses, + BlockRequests: "transparent", + }, + }, + }, + } + + data, err := json.Marshal(allowListPolicy) + if err != nil { + return nil + } + + return data +} + // ResourceOperation represents a function that changes configuration in relation to an unstructured resource. type ResourceOperation func(resource *v1beta1.DosProtectedResource, ingExes []*IngressEx, mergeableIngresses []*MergeableIngresses, vsExes []*VirtualServerEx) (Warnings, error) @@ -1862,6 +1910,10 @@ func appProtectDosLogConfFileName(namespace string, name string) string { return fmt.Sprintf("%s%s_%s.json", appProtectDosLogConfFolder, namespace, name) } +func appProtectDosAllowListFileName(namespace string, name string) string { + return fmt.Sprintf("%s%s_%s.json", appProtectDosAllowListFolder, namespace, name) +} + // DeleteAppProtectDosPolicy updates Ingresses and VirtualServers that use AP Dos Policy after that policy is deleted func (cnf *Configurator) DeleteAppProtectDosPolicy(resource *unstructured.Unstructured) { cnf.nginxManager.DeleteAppProtectResourceFile(appProtectDosPolicyFileName(resource.GetNamespace(), resource.GetName())) @@ -1872,6 +1924,11 @@ func (cnf *Configurator) DeleteAppProtectDosLogConf(resource *unstructured.Unstr cnf.nginxManager.DeleteAppProtectResourceFile(appProtectDosLogConfFileName(resource.GetNamespace(), resource.GetName())) } +// DeleteAppProtectDosAllowList updates Ingresses and VirtualServers that use AP Allow List Configuration after that policy is deleted +func (cnf *Configurator) DeleteAppProtectDosAllowList(obj *v1beta1.DosProtectedResource) { + cnf.nginxManager.DeleteAppProtectResourceFile(appProtectDosAllowListFileName(obj.Namespace, obj.Name)) +} + // AddInternalRouteConfig adds internal route server to NGINX Configuration and reloads NGINX func (cnf *Configurator) AddInternalRouteConfig() error { cnf.staticCfgParams.EnableInternalRoutes = true diff --git a/internal/configs/configurator_test.go b/internal/configs/configurator_test.go index 7bcfb0279a..99ca767e13 100644 --- a/internal/configs/configurator_test.go +++ b/internal/configs/configurator_test.go @@ -1,6 +1,7 @@ package configs import ( + "encoding/json" "os" "reflect" "testing" @@ -15,6 +16,7 @@ import ( "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" "github.com/nginxinc/kubernetes-ingress/internal/nginx" conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" + "github.com/nginxinc/kubernetes-ingress/pkg/apis/dos/v1beta1" ) func createTestStaticConfigParams() *StaticConfigParams { @@ -1651,3 +1653,64 @@ var ( }, } ) + +func TestGenerateApDosAllowListFileContent(t *testing.T) { + tests := []struct { + name string + allowList []v1beta1.AllowListEntry + want []byte + wantErr bool + }{ + { + name: "Empty allow list", + allowList: []v1beta1.AllowListEntry{}, + want: []byte(`{"policy":{"ip-address-lists":[{"ipAddresses":[],"blockRequests":"transparent"}]}}`), + wantErr: false, + }, + { + name: "Single valid IPv4 entry", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "192.168.1.1/32"}, + }, + want: []byte(`{"policy":{"ip-address-lists":[{"ipAddresses":[{"ipAddress":"192.168.1.1/32"}],"blockRequests":"transparent"}]}}`), + wantErr: false, + }, + { + name: "Single valid IPv6 entry", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"}, + }, + want: []byte(`{"policy":{"ip-address-lists":[{"ipAddresses":[{"ipAddress":"2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"}],"blockRequests":"transparent"}]}}`), + wantErr: false, + }, + { + name: "Multiple valid entries", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "192.168.1.1/32"}, + {IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"}, + }, + want: []byte(`{"policy":{"ip-address-lists":[{"ipAddresses":[{"ipAddress":"192.168.1.1/32"},{"ipAddress":"2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"}],"blockRequests":"transparent"}]}}`), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := generateApDosAllowListFileContent(tt.allowList) + if (got == nil) != tt.wantErr { + t.Errorf("generateApDosAllowListFileContent() error = %v, wantErr %v", got == nil, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { + var gotFormatted, wantFormatted interface{} + if err := json.Unmarshal(got, &gotFormatted); err != nil { + t.Errorf("Failed to unmarshal got: %v", err) + } + if err := json.Unmarshal(tt.want, &wantFormatted); err != nil { + t.Errorf("Failed to unmarshal want: %v", err) + } + t.Errorf("generateApDosAllowListFileContent() = \n%#v, \nwant \n%#v", gotFormatted, wantFormatted) + } + }) + } +} diff --git a/internal/configs/dos.go b/internal/configs/dos.go index eb994cea1e..f439aac4cf 100644 --- a/internal/configs/dos.go +++ b/internal/configs/dos.go @@ -11,6 +11,7 @@ type appProtectDosResource struct { AppProtectDosAccessLogDst string AppProtectDosPolicyFile string AppProtectDosLogConfFile string + AppProtectDosAllowListPath string } func getAppProtectDosResource(dosEx *DosEx) *appProtectDosResource { @@ -26,6 +27,10 @@ func getAppProtectDosResource(dosEx *DosEx) *appProtectDosResource { } dosResource.AppProtectDosName = protected.Namespace + "/" + protected.Name + "/" + protected.Spec.Name + if protected.Spec.AllowList != nil { + dosResource.AppProtectDosAllowListPath = appProtectDosAllowListFileName(protected.Namespace, protected.Name) + } + if protected.Spec.ApDosMonitor != nil { dosResource.AppProtectDosMonitorURI = protected.Spec.ApDosMonitor.URI dosResource.AppProtectDosMonitorProtocol = protected.Spec.ApDosMonitor.Protocol diff --git a/internal/configs/dos_test.go b/internal/configs/dos_test.go index 7abef5cdb5..1ae17339a8 100644 --- a/internal/configs/dos_test.go +++ b/internal/configs/dos_test.go @@ -68,6 +68,25 @@ func TestUpdateApDosResource(t *testing.T) { }, }, } + appProtectDosProtectedWithAllowList := &v1beta1.DosProtectedResource{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{ + Name: "dosWithAllowList", + Namespace: "test-ns", + }, + Spec: v1beta1.DosProtectedResourceSpec{ + Enable: true, + Name: "dos-protected", + ApDosMonitor: &v1beta1.ApDosMonitor{ + URI: "example.com", + }, + DosAccessLogDest: "127.0.0.1:5561", + AllowList: []v1beta1.AllowListEntry{ + {IPWithMask: "192.168.1.0/24"}, + {IPWithMask: "10.0.0.0/8"}, + }, + }, + } tests := []struct { dosProtectedEx *DosEx @@ -127,6 +146,21 @@ func TestUpdateApDosResource(t *testing.T) { }, msg: "app protect dos policy and log conf", }, + { + dosProtectedEx: &DosEx{ + DosProtected: appProtectDosProtectedWithAllowList, + DosPolicy: appProtectDosPolicy, + }, + expected: &appProtectDosResource{ + AppProtectDosEnable: "on", + AppProtectDosName: "test-ns/dosWithAllowList/dos-protected", + AppProtectDosMonitorURI: "example.com", + AppProtectDosAccessLogDst: "syslog:server=127.0.0.1:5561", + AppProtectDosPolicyFile: "/etc/nginx/dos/policies/test-ns_test-name.json", + AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/test-ns_dosWithAllowList.json", + }, + msg: "app protect dos with allow list", + }, } for _, test := range tests { diff --git a/internal/configs/ingress.go b/internal/configs/ingress.go index 53ac2636fd..364af3dc1e 100644 --- a/internal/configs/ingress.go +++ b/internal/configs/ingress.go @@ -190,6 +190,7 @@ func generateNginxCfg(p NginxCfgParams) (version1.IngressNginxConfig, Warnings) server.AppProtectDosMonitorProtocol = p.dosResource.AppProtectDosMonitorProtocol server.AppProtectDosMonitorTimeout = p.dosResource.AppProtectDosMonitorTimeout server.AppProtectDosName = p.dosResource.AppProtectDosName + server.AppProtectDosAllowListPath = p.dosResource.AppProtectDosAllowListPath server.AppProtectDosAccessLogDst = p.dosResource.AppProtectDosAccessLogDst server.AppProtectDosPolicyFile = p.dosResource.AppProtectDosPolicyFile server.AppProtectDosLogConfFile = p.dosResource.AppProtectDosLogConfFile diff --git a/internal/configs/ingress_test.go b/internal/configs/ingress_test.go index c222116a94..ede924c4d3 100644 --- a/internal/configs/ingress_test.go +++ b/internal/configs/ingress_test.go @@ -2398,13 +2398,14 @@ func TestGenerateNginxCfgForAppProtectDos(t *testing.T) { isPlus := true configParams := NewDefaultConfigParams(isPlus) dosResource := &appProtectDosResource{ - AppProtectDosEnable: "on", - AppProtectDosName: "dos.example.com", - AppProtectDosMonitorURI: "monitor-name", - AppProtectDosAccessLogDst: "access-log-dest", - AppProtectDosPolicyFile: "/etc/nginx/dos/policies/default_policy", - AppProtectDosLogEnable: true, - AppProtectDosLogConfFile: "/etc/nginx/dos/logconfs/default_logconf syslog:server=127.0.0.1:514", + AppProtectDosEnable: "on", + AppProtectDosName: "dos.example.com", + AppProtectDosMonitorURI: "monitor-name", + AppProtectDosAccessLogDst: "access-log-dest", + AppProtectDosPolicyFile: "/etc/nginx/dos/policies/default_policy", + AppProtectDosLogEnable: true, + AppProtectDosLogConfFile: "/etc/nginx/dos/logconfs/default_logconf syslog:server=127.0.0.1:514", + AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/default_dos", } staticCfgParams := &StaticConfigParams{ MainAppProtectDosLoadModule: true, @@ -2414,6 +2415,7 @@ func TestGenerateNginxCfgForAppProtectDos(t *testing.T) { expected.Servers[0].AppProtectDosEnable = "on" expected.Servers[0].AppProtectDosPolicyFile = "/etc/nginx/dos/policies/default_policy" expected.Servers[0].AppProtectDosLogConfFile = "/etc/nginx/dos/logconfs/default_logconf syslog:server=127.0.0.1:514" + expected.Servers[0].AppProtectDosAllowListPath = "/etc/nginx/dos/allowlist/default_dos" expected.Servers[0].AppProtectDosLogEnable = true expected.Servers[0].AppProtectDosName = "dos.example.com" expected.Servers[0].AppProtectDosMonitorURI = "monitor-name" @@ -2465,13 +2467,14 @@ func TestGenerateNginxCfgForMergeableIngressesForAppProtectDos(t *testing.T) { isPlus := true configParams := NewDefaultConfigParams(isPlus) dosResource := &appProtectDosResource{ - AppProtectDosEnable: "on", - AppProtectDosName: "dos.example.com", - AppProtectDosMonitorURI: "monitor-name", - AppProtectDosAccessLogDst: "access-log-dest", - AppProtectDosPolicyFile: "/etc/nginx/dos/policies/default_policy", - AppProtectDosLogEnable: true, - AppProtectDosLogConfFile: "/etc/nginx/dos/logconfs/default_logconf syslog:server=127.0.0.1:514", + AppProtectDosEnable: "on", + AppProtectDosName: "dos.example.com", + AppProtectDosMonitorURI: "monitor-name", + AppProtectDosAccessLogDst: "access-log-dest", + AppProtectDosPolicyFile: "/etc/nginx/dos/policies/default_policy", + AppProtectDosLogEnable: true, + AppProtectDosLogConfFile: "/etc/nginx/dos/logconfs/default_logconf syslog:server=127.0.0.1:514", + AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/default_dos", } staticCfgParams := &StaticConfigParams{ MainAppProtectDosLoadModule: true, @@ -2481,6 +2484,7 @@ func TestGenerateNginxCfgForMergeableIngressesForAppProtectDos(t *testing.T) { expected.Servers[0].AppProtectDosEnable = "on" expected.Servers[0].AppProtectDosPolicyFile = "/etc/nginx/dos/policies/default_policy" expected.Servers[0].AppProtectDosLogConfFile = "/etc/nginx/dos/logconfs/default_logconf syslog:server=127.0.0.1:514" + expected.Servers[0].AppProtectDosAllowListPath = "/etc/nginx/dos/allowlist/default_dos" expected.Servers[0].AppProtectDosLogEnable = true expected.Servers[0].AppProtectDosName = "dos.example.com" expected.Servers[0].AppProtectDosMonitorURI = "monitor-name" diff --git a/internal/configs/version1/__snapshots__/template_test.snap b/internal/configs/version1/__snapshots__/template_test.snap index 913e95100b..f56992a046 100644 --- a/internal/configs/version1/__snapshots__/template_test.snap +++ b/internal/configs/version1/__snapshots__/template_test.snap @@ -558,6 +558,7 @@ server { access_log /var/log/dos log_dos if=$loggable; app_protect_dos_monitor uri=/path/to/monitor protocol=http1 timeout=30; app_protect_dos_name "testdos"; + app_protect_dos_access_file "/etc/nginx/dos/allowlist/default_test.example.com"; if ($scheme = http) { @@ -653,6 +654,7 @@ server { access_log /var/log/dos log_dos if=$loggable; app_protect_dos_monitor uri=/path/to/monitor protocol=http1 timeout=30; app_protect_dos_name "testdos"; + app_protect_dos_access_file "/etc/nginx/dos/allowlist/default_test.example.com"; if ($scheme = http) { diff --git a/internal/configs/version1/config.go b/internal/configs/version1/config.go index 85d8c2006a..6677bde3f6 100644 --- a/internal/configs/version1/config.go +++ b/internal/configs/version1/config.go @@ -123,6 +123,7 @@ type Server struct { AppProtectDosMonitorProtocol string AppProtectDosMonitorTimeout uint64 AppProtectDosName string + AppProtectDosAllowListPath string AppProtectDosAccessLogDst string SpiffeCerts bool diff --git a/internal/configs/version1/nginx-plus.ingress.tmpl b/internal/configs/version1/nginx-plus.ingress.tmpl index 55b6439d24..4f9fdbf6bb 100644 --- a/internal/configs/version1/nginx-plus.ingress.tmpl +++ b/internal/configs/version1/nginx-plus.ingress.tmpl @@ -107,6 +107,7 @@ server { {{- end}} {{- end}} {{if $server.AppProtectDosName}}app_protect_dos_name "{{$server.AppProtectDosName}}";{{end}} + {{if $server.AppProtectDosAllowListPath}}app_protect_dos_access_file "{{$server.AppProtectDosAllowListPath}}";{{end}} {{- end}} {{if not $server.GRPCOnly}} diff --git a/internal/configs/version1/template_test.go b/internal/configs/version1/template_test.go index 19088a40e2..bc620d8f42 100644 --- a/internal/configs/version1/template_test.go +++ b/internal/configs/version1/template_test.go @@ -1767,6 +1767,7 @@ var ( AppProtectDosMonitorTimeout: 30, AppProtectDosName: "testdos", AppProtectDosAccessLogDst: "/var/log/dos", + AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/default_test.example.com", }, }, Upstreams: []Upstream{testUpstream}, diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap index 4fa5c98686..5a57399f9f 100644 --- a/internal/configs/version2/__snapshots__/templates_test.snap +++ b/internal/configs/version2/__snapshots__/templates_test.snap @@ -179,6 +179,7 @@ server { app_protect_dos_enable on; app_protect_dos_name "my-dos-coffee"; + app_protect_dos_access_file "/etc/nginx/dos/allowlist/default_test.example.com"; app_protect_dos_policy_file /test/policy.json; app_protect_dos_security_log_enable on; app_protect_dos_security_log /test/log.json; @@ -620,6 +621,7 @@ server { app_protect_dos_enable on; app_protect_dos_name "my-dos-coffee"; + app_protect_dos_access_file "/etc/nginx/dos/allowlist/default_test.example.com"; app_protect_dos_policy_file /test/policy.json; app_protect_dos_security_log_enable on; app_protect_dos_security_log /test/log.json; diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index a2266fc98e..2ee1884688 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -159,6 +159,7 @@ type WAF struct { type Dos struct { Enable string Name string + AllowListPath string ApDosPolicy string ApDosSecurityLogEnable bool ApDosLogConf string diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index 935d11e686..7c6af65f55 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -283,6 +283,10 @@ server { app_protect_dos_name "{{ .Name }}"; {{- end }} + {{- if .AllowListPath }} + app_protect_dos_access_file "{{ .AllowListPath }}"; + {{- end }} + {{- if .ApDosPolicy }} app_protect_dos_policy_file {{ .ApDosPolicy }}; {{- end }} @@ -523,6 +527,10 @@ server { app_protect_dos_name "{{ .Name }}"; {{- end }} + {{- if .AllowListPath }} + app_protect_dos_access_file "{{ .AllowListPath }}"; + {{- end }} + {{- if .ApDosPolicy }} app_protect_dos_policy_file {{ .ApDosPolicy }}; {{- end }} diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index 0e4f8ead22..811bb4c6c4 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -704,6 +704,7 @@ func vsConfig() VirtualServerConfig { ApDosSecurityLogEnable: true, ApDosLogConf: "/test/log.json", ApDosMonitorTimeout: 30, + AllowListPath: "/etc/nginx/dos/allowlist/default_test.example.com", }, Snippets: []string{"# server snippet"}, InternalRedirectLocations: []InternalRedirectLocation{ diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index d2fc020f8d..32d561def6 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -3005,6 +3005,7 @@ func generateDosCfg(dosResource *appProtectDosResource) *version2.Dos { dos := &version2.Dos{} dos.Enable = dosResource.AppProtectDosEnable dos.Name = dosResource.AppProtectDosName + dos.AllowListPath = dosResource.AppProtectDosAllowListPath dos.ApDosMonitorURI = dosResource.AppProtectDosMonitorURI dos.ApDosMonitorProtocol = dosResource.AppProtectDosMonitorProtocol dos.ApDosMonitorTimeout = dosResource.AppProtectDosMonitorTimeout diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index abb4dc1886..f292d960ab 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -4531,6 +4531,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) AppProtectDosAccessLogDst: "svc.dns.com:123", AppProtectDosPolicyFile: "", AppProtectDosLogConfFile: "", + AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/default_coffee", }, "/tea": { AppProtectDosEnable: "on", @@ -4542,6 +4543,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) AppProtectDosAccessLogDst: "svc.dns.com:123", AppProtectDosPolicyFile: "", AppProtectDosLogConfFile: "", + AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/default_tea", }, "/juice": { AppProtectDosEnable: "on", @@ -4553,6 +4555,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) AppProtectDosAccessLogDst: "svc.dns.com:123", AppProtectDosPolicyFile: "", AppProtectDosLogConfFile: "", + AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/default_juice", }, } @@ -4741,6 +4744,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) ApDosMonitorURI: "test.example.com", ApDosMonitorProtocol: "http", ApDosAccessLogDest: "svc.dns.com:123", + AllowListPath: "/etc/nginx/dos/allowlist/default_coffee", }, }, { @@ -4763,6 +4767,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) ApDosMonitorURI: "test.example.com", ApDosMonitorProtocol: "http", ApDosAccessLogDest: "svc.dns.com:123", + AllowListPath: "/etc/nginx/dos/allowlist/default_coffee", }, }, { @@ -4785,6 +4790,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) ApDosMonitorURI: "test.example.com", ApDosMonitorProtocol: "http", ApDosAccessLogDest: "svc.dns.com:123", + AllowListPath: "/etc/nginx/dos/allowlist/default_tea", }, }, { @@ -4802,6 +4808,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) ApDosMonitorURI: "test.example.com", ApDosMonitorProtocol: "http", ApDosAccessLogDest: "svc.dns.com:123", + AllowListPath: "/etc/nginx/dos/allowlist/default_juice", }, ServiceName: "juice-svc-v1", IsVSR: true, @@ -4823,6 +4830,7 @@ func TestGenerateVirtualServerConfigForVirtualServerRoutesWithDos(t *testing.T) ApDosMonitorURI: "test.example.com", ApDosMonitorProtocol: "http", ApDosAccessLogDest: "svc.dns.com:123", + AllowListPath: "/etc/nginx/dos/allowlist/default_juice", }, ServiceName: "juice-svc-v2", IsVSR: true, diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index fa20ad1979..b88cf6b687 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -2065,6 +2065,7 @@ func (lbc *LoadBalancerController) processAppProtectDosChanges(changes []appprot case *appprotectdos.DosProtectedResourceEx: glog.V(3).Infof("handling change DELETE for DOS protected %s/%s", impl.Obj.Namespace, impl.Obj.Name) + lbc.configurator.DeleteAppProtectDosAllowList(impl.Obj) resources := lbc.configuration.FindResourcesForAppProtectDosProtected(impl.Obj.Namespace, impl.Obj.Name) resourceExes := lbc.createExtendedResources(resources) warnings, err := lbc.configurator.AddOrUpdateResourcesThatUseDosProtected(resourceExes.IngressExes, resourceExes.MergeableIngresses, resourceExes.VirtualServerExes) diff --git a/pkg/apis/dos/v1beta1/types.go b/pkg/apis/dos/v1beta1/types.go index 0a487da491..a0984570ac 100644 --- a/pkg/apis/dos/v1beta1/types.go +++ b/pkg/apis/dos/v1beta1/types.go @@ -27,6 +27,13 @@ type DosProtectedResourceSpec struct { // ApDosPolicy is the namespace/name of a ApDosPolicy resource ApDosPolicy string `json:"apDosPolicy"` DosSecurityLog *DosSecurityLog `json:"dosSecurityLog"` + // AllowList is a list of allowed IPs and subnet masks + AllowList []AllowListEntry `json:"allowList,omitempty"` +} + +// AllowListEntry represents an IP address and a subnet mask. +type AllowListEntry struct { + IPWithMask string `json:"ipWithMask"` } // ApDosMonitor is how NGINX App Protect DoS monitors the stress level of the protected object. The monitor requests are sent from localhost (127.0.0.1). Default value: URI - None, protocol - http1, timeout - NGINX App Protect DoS default. diff --git a/pkg/apis/dos/v1beta1/zz_generated.deepcopy.go b/pkg/apis/dos/v1beta1/zz_generated.deepcopy.go index 69ccdbc26a..f43d265330 100644 --- a/pkg/apis/dos/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/dos/v1beta1/zz_generated.deepcopy.go @@ -9,6 +9,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AllowListEntry) DeepCopyInto(out *AllowListEntry) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowListEntry. +func (in *AllowListEntry) DeepCopy() *AllowListEntry { + if in == nil { + return nil + } + out := new(AllowListEntry) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApDosMonitor) DeepCopyInto(out *ApDosMonitor) { *out = *in @@ -98,6 +114,11 @@ func (in *DosProtectedResourceSpec) DeepCopyInto(out *DosProtectedResourceSpec) *out = new(DosSecurityLog) **out = **in } + if in.AllowList != nil { + in, out := &in.AllowList, &out.AllowList + *out = make([]AllowListEntry, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/apis/dos/validation/dos.go b/pkg/apis/dos/validation/dos.go index 0ea3f17071..36c5f49bf0 100644 --- a/pkg/apis/dos/validation/dos.go +++ b/pkg/apis/dos/validation/dos.go @@ -2,6 +2,7 @@ package validation import ( "fmt" + "net" "net/url" "regexp" "strconv" @@ -43,6 +44,13 @@ func ValidateDosProtectedResource(protected *v1beta1.DosProtectedResource) error } } + if protected.Spec.AllowList != nil { + err = ValidateAppProtectDosAllowList(protected.Spec.AllowList) + if err != nil { + return fmt.Errorf("error validating DosProtectedResource: %v invalid field: %v err: %w", protected.Name, "allowList", err) + } + } + // dosAccessLogDest if protected.Spec.DosAccessLogDest != "" { err = validateAppProtectDosLogDest(protected.Spec.DosAccessLogDest) @@ -189,3 +197,19 @@ func ValidateAppProtectDosPolicy(policy *unstructured.Unstructured) error { return nil } + +// ValidateAppProtectDosAllowList validates AllowList if all IP and Mask are correct +func ValidateAppProtectDosAllowList(allowList []v1beta1.AllowListEntry) error { + for _, entry := range allowList { + ipValid := isValidIPWithMask(entry.IPWithMask) + if !ipValid { + return fmt.Errorf("Invalid IP with subnet mask: %s", entry.IPWithMask) + } + } + return nil +} + +func isValidIPWithMask(ipWithMask string) bool { + _, _, err := net.ParseCIDR(ipWithMask) + return err == nil +} diff --git a/pkg/apis/dos/validation/dos_test.go b/pkg/apis/dos/validation/dos_test.go index 9cc0f82de7..550abc6a85 100644 --- a/pkg/apis/dos/validation/dos_test.go +++ b/pkg/apis/dos/validation/dos_test.go @@ -487,3 +487,124 @@ func TestValidateAppProtectDosLogDest_ValidOnDestinationStdErr(t *testing.T) { t.Error(err) } } + +func TestValidateAppProtectDosAllowList(t *testing.T) { + tests := []struct { + name string + allowList []v1beta1.AllowListEntry + wantErr bool + }{ + { + name: "Empty allow list", + allowList: []v1beta1.AllowListEntry{}, + wantErr: false, + }, + { + name: "Single valid IPv4 entry", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "192.168.1.1/32"}, + }, + wantErr: false, + }, + { + name: "Single valid IPv6 entry", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"}, + }, + wantErr: false, + }, + { + name: "Multiple valid entries", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "192.168.1.1/32"}, + {IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"}, + }, + wantErr: false, + }, + { + name: "Invalid IPv4 entry", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "192.168.1.1445/32"}, + }, + wantErr: true, + }, + { + name: "Invalid IPv6 entry", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334:3454/128"}, + }, + wantErr: true, + }, + { + name: "Invalid subnet mask", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "192.168.1.1/abc"}, + }, + wantErr: true, + }, + { + name: "Invalid IPv6 subnet mask", + allowList: []v1beta1.AllowListEntry{ + {IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/199"}, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateAppProtectDosAllowList(tt.allowList) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateAppProtectDosAllowList() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestIsValidIPWithMask(t *testing.T) { + tests := []struct { + name string + ipWithMask string + want bool + }{ + { + name: "Valid IPv4 address with mask", + ipWithMask: "192.168.1.1/32", + want: true, + }, + { + name: "Valid IPv6 address with mask", + ipWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128", + want: true, + }, + { + name: "Invalid IPv4 address with mask", + ipWithMask: "192.168.1.14444/33", + want: false, + }, + { + name: "Invalid IPv6 address with mask", + ipWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334:343434/128", + want: false, + }, + { + name: "Invalid subnet mask", + ipWithMask: "192.168.1.1/abc", + want: false, + }, + { + name: "No subnet mask", + ipWithMask: "192.168.1.1", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isValidIPWithMask(tt.ipWithMask) + if got != tt.want { + t.Errorf("isValidIPWithMask() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/tests/data/dos/dos-accesslog.yaml b/tests/data/dos/dos-accesslog.yaml new file mode 100644 index 0000000000..c515a36dbd --- /dev/null +++ b/tests/data/dos/dos-accesslog.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: accesslog +spec: + replicas: 1 + selector: + matchLabels: + app: accesslog + template: + metadata: + labels: + app: accesslog + spec: + containers: + - name: accesslog + image: balabit/syslog-ng:4.3.0 + ports: + - containerPort: 514 + - containerPort: 601 +--- +apiVersion: v1 +kind: Service +metadata: + name: accesslog-svc +spec: + ports: + - port: 514 + targetPort: 514 + protocol: UDP + selector: + app: accesslog diff --git a/tests/data/dos/dos-protected.yaml b/tests/data/dos/dos-protected.yaml index 9f2796dc08..827d1d48c2 100644 --- a/tests/data/dos/dos-protected.yaml +++ b/tests/data/dos/dos-protected.yaml @@ -10,8 +10,10 @@ spec: uri: "dos.example.com" protocol: "http1" timeout: 5 - dosAccessLogDest: "127.0.0.1:5561" + dosAccessLogDest: "accesslog-svc..svc.cluster.local:514" dosSecurityLog: enable: true apDosLogConf: "/doslogconf" dosLogDest: "syslog-svc..svc.cluster.local:514" + allowList: + - ipWithMask: "10.10.10.10/32" diff --git a/tests/data/virtual-server-dos/dos-protected.yaml b/tests/data/virtual-server-dos/dos-protected.yaml index c593a1beec..b9f885a6f6 100644 --- a/tests/data/virtual-server-dos/dos-protected.yaml +++ b/tests/data/virtual-server-dos/dos-protected.yaml @@ -8,8 +8,10 @@ spec: apDosPolicy: "dospolicy" apDosMonitor: uri: "dos.example.com" - dosAccessLogDest: "127.0.0.1:5561" + dosAccessLogDest: "accesslog-svc..svc.cluster.local:514" dosSecurityLog: enable: true apDosLogConf: "doslogconf" dosLogDest: "syslog-svc..svc.cluster.local:514" + allowList: + - ipWithMask: "10.10.10.10/32" diff --git a/tests/suite/fixtures/ic_fixtures.py b/tests/suite/fixtures/ic_fixtures.py index e75f1ede87..285cbb7e6a 100644 --- a/tests/suite/fixtures/ic_fixtures.py +++ b/tests/suite/fixtures/ic_fixtures.py @@ -268,11 +268,16 @@ def crd_ingress_controller_with_dos( print("------------------------- Create syslog svc -----------------------") src_syslog_yaml = f"{TEST_DATA}/dos/dos-syslog.yaml" create_items_from_yaml(kube_apis, src_syslog_yaml, namespace) + + print("------------------------- Create accesslog svc -----------------------") + src_accesslog_yaml = f"{TEST_DATA}/dos/dos-accesslog.yaml" + create_items_from_yaml(kube_apis, src_accesslog_yaml, namespace) + before = time.time() wait_until_all_pods_are_ready(kube_apis.v1, namespace) after = time.time() print(f"All pods came up in {int(after-before)} seconds") - print(f"syslog svc was created") + print(f"syslog and accesslog svc was created") print("------------------------- Create dos arbitrator -----------------------") dos_arbitrator_name = create_dos_arbitrator( @@ -341,6 +346,8 @@ def fin(): delete_ingress_controller(kube_apis.apps_v1_api, name, cli_arguments["deployment-type"], namespace) print("Remove the syslog svc:") delete_items_from_yaml(kube_apis, src_syslog_yaml, namespace) + print("Remove the accesslog svc:") + delete_items_from_yaml(kube_apis, src_accesslog_yaml, namespace) request.addfinalizer(fin) diff --git a/tests/suite/test_dos.py b/tests/suite/test_dos.py index 3dfc93e995..adec26a82f 100644 --- a/tests/suite/test_dos.py +++ b/tests/suite/test_dos.py @@ -57,12 +57,14 @@ class DosSetup: Encapsulate the example details. Attributes: req_url (str): + protected_name (str) pol_name (str): log_name (str): """ - def __init__(self, req_url, pol_name, log_name): + def __init__(self, req_url, protected_name, pol_name, log_name): self.req_url = req_url + self.protected_name = protected_name self.pol_name = pol_name self.log_name = log_name @@ -138,7 +140,7 @@ def fin(): request.addfinalizer(fin) - return DosSetup(req_url, pol_name, log_name) + return DosSetup(req_url, protected_name, pol_name, log_name) @pytest.mark.dos @@ -178,7 +180,8 @@ def test_ap_nginx_config_entries( f"app_protect_dos_security_log_enable on;", f"app_protect_dos_security_log /etc/nginx/dos/logconfs/{test_namespace}_{dos_setup.log_name}.json syslog:server=syslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514;", f"set $loggable '0';", - f"access_log syslog:server=127.0.0.1:5561 log_dos if=$loggable;", + f"access_log syslog:server=accesslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514 log_dos if=$loggable;", + f'app_protect_dos_access_file "/etc/nginx/dos/allowlist/{test_namespace}_{dos_setup.protected_name}.json";', ] conf_nginx_directive = ["app_protect_dos_api on;", "location = /dashboard-dos.html"] @@ -241,22 +244,64 @@ def test_dos_sec_logs_on( wait_before_test(5) response = requests.get(dos_setup.req_url, headers={"host": "dos.example.com"}, verify=False) print(response.text) - wait_before_test(10) print(f"log_loc {log_loc} syslog_pod {syslog_pod} namespace {ingress_controller_prerequisites.namespace}") - log_contents = get_file_contents(kube_apis.v1, log_loc, syslog_pod, ingress_controller_prerequisites.namespace) - - delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) - print(log_contents) + log_contents = "" retry = 0 while 'product="app-protect-dos"' not in log_contents and retry < 20: - wait_before_test() + wait_before_test(1) + log_contents = get_file_contents( + kube_apis.v1, log_loc, syslog_pod, ingress_controller_prerequisites.namespace, print_log=False + ) retry += 1 + print(log_contents) + + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + assert f'vs_name="{test_namespace}/dos-protected/name"' in log_contents assert "bad_actor" in log_contents + def test_dos_allowlist( + self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_dos, dos_setup, test_namespace + ): + """ + Test App Protect Dos: Block bad clients attack with learning + """ + log_loc = f"/var/log/messages" + print("----------------------- Get accesslog pod name ----------------------") + accesslog_pod = self.getPodNameThatContains(kube_apis, ingress_controller_prerequisites.namespace, "accesslog") + assert "accesslog" in accesslog_pod + clear_file_contents(kube_apis.v1, log_loc, accesslog_pod, ingress_controller_prerequisites.namespace) + print(f"log_loc {log_loc} syslog_pod {accesslog_pod} namespace {ingress_controller_prerequisites.namespace}") + + print("------------------------- Deploy ingress -----------------------------") + create_ingress_with_dos_annotations(kube_apis, src_ing_yaml, test_namespace, test_namespace + "/dos-protected") + get_first_ingress_host_from_yaml(src_ing_yaml) + + print("----------------------- Send request to check allowlist ----------------------") + wait_before_test(5) + response = requests.get( + dos_setup.req_url, headers={"host": "dos.example.com", "X-Forwarded-For": "10.10.10.10"}, verify=False + ) + print(response.text) + + retry = 0 + log_contents = "" + while 'reason=AllowList"' not in log_contents and retry < 20: + wait_before_test(1) + log_contents = get_file_contents( + kube_apis.v1, log_loc, accesslog_pod, ingress_controller_prerequisites.namespace, print_log=False + ) + retry += 1 + + delete_items_from_yaml(kube_apis, src_ing_yaml, test_namespace) + + print(log_contents) + + assert "reason=Allowlist" in log_contents + @pytest.mark.dos_learning def test_dos_under_attack_with_learning( self, kube_apis, ingress_controller_prerequisites, crd_ingress_controller_with_dos, dos_setup, test_namespace diff --git a/tests/suite/test_virtual_server_dos.py b/tests/suite/test_virtual_server_dos.py index 92ae2e5cb4..92caf0387c 100644 --- a/tests/suite/test_virtual_server_dos.py +++ b/tests/suite/test_virtual_server_dos.py @@ -83,12 +83,14 @@ class DosSetup: Encapsulate the example details. Attributes: req_url (str): + protected_name (str) pol_name (str): log_name (str): """ - def __init__(self, req_url, pol_name, log_name): + def __init__(self, req_url, protected_name, pol_name, log_name): self.req_url = req_url + self.protected_name = protected_name self.pol_name = pol_name self.log_name = log_name @@ -148,7 +150,7 @@ def fin(): request.addfinalizer(fin) - return DosSetup(req_url, pol_name, log_name) + return DosSetup(req_url, protected_name, pol_name, log_name) @pytest.mark.dos @@ -238,6 +240,8 @@ def test_vs_with_dos_config( f"app_protect_dos_policy_file /etc/nginx/dos/policies/{test_namespace}_{dos_setup.pol_name}.json;", f"app_protect_dos_security_log_enable on;", f"app_protect_dos_security_log /etc/nginx/dos/logconfs/{test_namespace}_{dos_setup.log_name}.json syslog:server=syslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514;", + f"access_log syslog:server=accesslog-svc.{ingress_controller_prerequisites.namespace}.svc.cluster.local:514 log_dos if=$loggable;", + f'app_protect_dos_access_file "/etc/nginx/dos/allowlist/{test_namespace}_{dos_setup.protected_name}.json";', ] print("\n confirm response for standard request") diff --git a/tests/suite/utils/custom_resources_utils.py b/tests/suite/utils/custom_resources_utils.py index 8c99c588f8..aaee0708d7 100644 --- a/tests/suite/utils/custom_resources_utils.py +++ b/tests/suite/utils/custom_resources_utils.py @@ -316,6 +316,7 @@ def create_dos_protected_from_yaml(custom_objects: CustomObjectsApi, yaml_manife "", ing_namespace ) dep["spec"]["apDosPolicy"] = dep["spec"]["apDosPolicy"].replace("", namespace) + dep["spec"]["dosAccessLogDest"] = dep["spec"]["dosAccessLogDest"].replace("", ing_namespace) custom_objects.create_namespaced_custom_object( "appprotectdos.f5.com", "v1beta1", namespace, "dosprotectedresources", dep )