Skip to content

Commit ad88fb9

Browse files
authored
NAP DOS - AllowList (#5824)
1 parent c5102cb commit ad88fb9

File tree

34 files changed

+517
-29
lines changed

34 files changed

+517
-29
lines changed

build/scripts/nap-dos.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
set -e
44

5-
mkdir -p /root/app_protect_dos /etc/nginx/dos/policies /etc/nginx/dos/logconfs /shared/cores /var/log/adm /var/run/adm
5+
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
66
chmod 777 /shared/cores /var/log/adm /var/run/adm /etc/app_protect_dos

config/crd/bases/appprotectdos.f5.com_dosprotectedresources.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ spec:
4242
description: DosProtectedResourceSpec defines the properties and values
4343
a DosProtectedResource can have.
4444
properties:
45+
allowList:
46+
description: AllowList is a list of allowed IPs and subnet masks
47+
items:
48+
description: AllowListEntry represents an IP address and a subnet
49+
mask.
50+
properties:
51+
ipWithMask:
52+
type: string
53+
type: object
54+
type: array
4555
apDosMonitor:
4656
description: 'ApDosMonitor is how NGINX App Protect DoS monitors the
4757
stress level of the protected object. The monitor requests are sent

deploy/crds-nap-dos.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@ spec:
187187
description: DosProtectedResourceSpec defines the properties and values
188188
a DosProtectedResource can have.
189189
properties:
190+
allowList:
191+
description: AllowList is a list of allowed IPs and subnet masks
192+
items:
193+
description: AllowListEntry represents an IP address and a subnet
194+
mask.
195+
properties:
196+
ipWithMask:
197+
type: string
198+
type: object
199+
type: array
190200
apDosMonitor:
191201
description: 'ApDosMonitor is how NGINX App Protect DoS monitors the
192202
stress level of the protected object. The monitor requests are sent

docs/content/installation/integrations/app-protect-dos/dos-protected.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ spec:
4040
|``dosSecurityLog.enable`` | Enables security log. | ``bool`` | No |
4141
|``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 |
4242
|``dosSecurityLog.dosLogDest`` | The log destination for the security log. Accepted variables are ``syslog:server=<ip-address &#124; localhost &#124; dns-name>:<port>``, ``stderr``, ``<absolute path to file>``. Default is ``"syslog:server=127.0.0.1:514"``. | ``string`` | No |
43+
|``allowList`` | List of allowed IP addresses and subnet masks. Each entry is represented by an `IPWithMask` string. | ``[]AllowListEntry`` | No |
4344
{{% /table %}}
4445

4546
### DosProtectedResource.apDosPolicy

examples/custom-resources/app-protect-dos/apdos-protected.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,8 @@ spec:
1515
enable: true
1616
apDosLogConf: "doslogconf"
1717
dosLogDest: "syslog-svc.default.svc.cluster.local:514"
18+
allowList:
19+
- ipWithMask: "192.168.1.1/24"
20+
- ipWithMask: "10.244.0.1/32"
21+
- ipWithMask: "2023::4ef3/128"
22+
- ipWithMask: "2034::2300/120"

examples/ingress-resources/app-protect-dos/apdos-protected.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,8 @@ spec:
1515
enable: true
1616
apDosLogConf: "doslogconf"
1717
dosLogDest: "syslog-svc.default.svc.cluster.local:514"
18+
allowList:
19+
- ipWithMask: "192.168.1.1/24"
20+
- ipWithMask: "10.244.0.1/32"
21+
- ipWithMask: "2023::4ef3/128"
22+
- ipWithMask: "2034::2300/120"

internal/configs/configurator.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const (
3737
appProtectUserSigIndex = "/etc/nginx/waf/nac-usersigs/index.conf"
3838
appProtectDosPolicyFolder = "/etc/nginx/dos/policies/"
3939
appProtectDosLogConfFolder = "/etc/nginx/dos/logconfs/"
40+
appProtectDosAllowListFolder = "/etc/nginx/dos/allowlist/"
4041
)
4142

4243
// 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
16791680

16801681
func (cnf *Configurator) updateDosResource(dosEx *DosEx) {
16811682
if dosEx != nil {
1683+
if dosEx.DosProtected != nil {
1684+
allowListFileName := appProtectDosAllowListFileName(dosEx.DosProtected.GetNamespace(), dosEx.DosProtected.GetName())
1685+
allowListContent := generateApDosAllowListFileContent(dosEx.DosProtected.Spec.AllowList)
1686+
cnf.nginxManager.CreateAppProtectResourceFile(allowListFileName, allowListContent)
1687+
}
16821688
if dosEx.DosPolicy != nil {
16831689
policyFileName := appProtectDosPolicyFileName(dosEx.DosPolicy.GetNamespace(), dosEx.DosPolicy.GetName())
16841690
policyContent := generateApResourceFileContent(dosEx.DosPolicy)
@@ -1738,6 +1744,48 @@ func generateApResourceFileContent(apResource *unstructured.Unstructured) []byte
17381744
return data
17391745
}
17401746

1747+
func generateApDosAllowListFileContent(allowList []v1beta1.AllowListEntry) []byte {
1748+
type IPAddress struct {
1749+
IPAddress string `json:"ipAddress"`
1750+
}
1751+
1752+
type IPAddressList struct {
1753+
IPAddresses []IPAddress `json:"ipAddresses"`
1754+
BlockRequests string `json:"blockRequests"`
1755+
}
1756+
1757+
type Policy struct {
1758+
IPAddressLists []IPAddressList `json:"ip-address-lists"`
1759+
}
1760+
1761+
type AllowListPolicy struct {
1762+
Policy Policy `json:"policy"`
1763+
}
1764+
1765+
ipAddresses := make([]IPAddress, len(allowList))
1766+
for i, entry := range allowList {
1767+
ipAddresses[i] = IPAddress{IPAddress: entry.IPWithMask}
1768+
}
1769+
1770+
allowListPolicy := AllowListPolicy{
1771+
Policy: Policy{
1772+
IPAddressLists: []IPAddressList{
1773+
{
1774+
IPAddresses: ipAddresses,
1775+
BlockRequests: "transparent",
1776+
},
1777+
},
1778+
},
1779+
}
1780+
1781+
data, err := json.Marshal(allowListPolicy)
1782+
if err != nil {
1783+
return nil
1784+
}
1785+
1786+
return data
1787+
}
1788+
17411789
// ResourceOperation represents a function that changes configuration in relation to an unstructured resource.
17421790
type ResourceOperation func(resource *v1beta1.DosProtectedResource, ingExes []*IngressEx, mergeableIngresses []*MergeableIngresses, vsExes []*VirtualServerEx) (Warnings, error)
17431791

@@ -1862,6 +1910,10 @@ func appProtectDosLogConfFileName(namespace string, name string) string {
18621910
return fmt.Sprintf("%s%s_%s.json", appProtectDosLogConfFolder, namespace, name)
18631911
}
18641912

1913+
func appProtectDosAllowListFileName(namespace string, name string) string {
1914+
return fmt.Sprintf("%s%s_%s.json", appProtectDosAllowListFolder, namespace, name)
1915+
}
1916+
18651917
// DeleteAppProtectDosPolicy updates Ingresses and VirtualServers that use AP Dos Policy after that policy is deleted
18661918
func (cnf *Configurator) DeleteAppProtectDosPolicy(resource *unstructured.Unstructured) {
18671919
cnf.nginxManager.DeleteAppProtectResourceFile(appProtectDosPolicyFileName(resource.GetNamespace(), resource.GetName()))
@@ -1872,6 +1924,11 @@ func (cnf *Configurator) DeleteAppProtectDosLogConf(resource *unstructured.Unstr
18721924
cnf.nginxManager.DeleteAppProtectResourceFile(appProtectDosLogConfFileName(resource.GetNamespace(), resource.GetName()))
18731925
}
18741926

1927+
// DeleteAppProtectDosAllowList updates Ingresses and VirtualServers that use AP Allow List Configuration after that policy is deleted
1928+
func (cnf *Configurator) DeleteAppProtectDosAllowList(obj *v1beta1.DosProtectedResource) {
1929+
cnf.nginxManager.DeleteAppProtectResourceFile(appProtectDosAllowListFileName(obj.Namespace, obj.Name))
1930+
}
1931+
18751932
// AddInternalRouteConfig adds internal route server to NGINX Configuration and reloads NGINX
18761933
func (cnf *Configurator) AddInternalRouteConfig() error {
18771934
cnf.staticCfgParams.EnableInternalRoutes = true

internal/configs/configurator_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package configs
22

33
import (
4+
"encoding/json"
45
"os"
56
"reflect"
67
"testing"
@@ -15,6 +16,7 @@ import (
1516
"github.com/nginxinc/kubernetes-ingress/internal/configs/version2"
1617
"github.com/nginxinc/kubernetes-ingress/internal/nginx"
1718
conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1"
19+
"github.com/nginxinc/kubernetes-ingress/pkg/apis/dos/v1beta1"
1820
)
1921

2022
func createTestStaticConfigParams() *StaticConfigParams {
@@ -1651,3 +1653,64 @@ var (
16511653
},
16521654
}
16531655
)
1656+
1657+
func TestGenerateApDosAllowListFileContent(t *testing.T) {
1658+
tests := []struct {
1659+
name string
1660+
allowList []v1beta1.AllowListEntry
1661+
want []byte
1662+
wantErr bool
1663+
}{
1664+
{
1665+
name: "Empty allow list",
1666+
allowList: []v1beta1.AllowListEntry{},
1667+
want: []byte(`{"policy":{"ip-address-lists":[{"ipAddresses":[],"blockRequests":"transparent"}]}}`),
1668+
wantErr: false,
1669+
},
1670+
{
1671+
name: "Single valid IPv4 entry",
1672+
allowList: []v1beta1.AllowListEntry{
1673+
{IPWithMask: "192.168.1.1/32"},
1674+
},
1675+
want: []byte(`{"policy":{"ip-address-lists":[{"ipAddresses":[{"ipAddress":"192.168.1.1/32"}],"blockRequests":"transparent"}]}}`),
1676+
wantErr: false,
1677+
},
1678+
{
1679+
name: "Single valid IPv6 entry",
1680+
allowList: []v1beta1.AllowListEntry{
1681+
{IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"},
1682+
},
1683+
want: []byte(`{"policy":{"ip-address-lists":[{"ipAddresses":[{"ipAddress":"2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"}],"blockRequests":"transparent"}]}}`),
1684+
wantErr: false,
1685+
},
1686+
{
1687+
name: "Multiple valid entries",
1688+
allowList: []v1beta1.AllowListEntry{
1689+
{IPWithMask: "192.168.1.1/32"},
1690+
{IPWithMask: "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128"},
1691+
},
1692+
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"}]}}`),
1693+
wantErr: false,
1694+
},
1695+
}
1696+
1697+
for _, tt := range tests {
1698+
t.Run(tt.name, func(t *testing.T) {
1699+
got := generateApDosAllowListFileContent(tt.allowList)
1700+
if (got == nil) != tt.wantErr {
1701+
t.Errorf("generateApDosAllowListFileContent() error = %v, wantErr %v", got == nil, tt.wantErr)
1702+
return
1703+
}
1704+
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
1705+
var gotFormatted, wantFormatted interface{}
1706+
if err := json.Unmarshal(got, &gotFormatted); err != nil {
1707+
t.Errorf("Failed to unmarshal got: %v", err)
1708+
}
1709+
if err := json.Unmarshal(tt.want, &wantFormatted); err != nil {
1710+
t.Errorf("Failed to unmarshal want: %v", err)
1711+
}
1712+
t.Errorf("generateApDosAllowListFileContent() = \n%#v, \nwant \n%#v", gotFormatted, wantFormatted)
1713+
}
1714+
})
1715+
}
1716+
}

internal/configs/dos.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type appProtectDosResource struct {
1111
AppProtectDosAccessLogDst string
1212
AppProtectDosPolicyFile string
1313
AppProtectDosLogConfFile string
14+
AppProtectDosAllowListPath string
1415
}
1516

1617
func getAppProtectDosResource(dosEx *DosEx) *appProtectDosResource {
@@ -26,6 +27,10 @@ func getAppProtectDosResource(dosEx *DosEx) *appProtectDosResource {
2627
}
2728
dosResource.AppProtectDosName = protected.Namespace + "/" + protected.Name + "/" + protected.Spec.Name
2829

30+
if protected.Spec.AllowList != nil {
31+
dosResource.AppProtectDosAllowListPath = appProtectDosAllowListFileName(protected.Namespace, protected.Name)
32+
}
33+
2934
if protected.Spec.ApDosMonitor != nil {
3035
dosResource.AppProtectDosMonitorURI = protected.Spec.ApDosMonitor.URI
3136
dosResource.AppProtectDosMonitorProtocol = protected.Spec.ApDosMonitor.Protocol

internal/configs/dos_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,25 @@ func TestUpdateApDosResource(t *testing.T) {
6868
},
6969
},
7070
}
71+
appProtectDosProtectedWithAllowList := &v1beta1.DosProtectedResource{
72+
TypeMeta: v1.TypeMeta{},
73+
ObjectMeta: v1.ObjectMeta{
74+
Name: "dosWithAllowList",
75+
Namespace: "test-ns",
76+
},
77+
Spec: v1beta1.DosProtectedResourceSpec{
78+
Enable: true,
79+
Name: "dos-protected",
80+
ApDosMonitor: &v1beta1.ApDosMonitor{
81+
URI: "example.com",
82+
},
83+
DosAccessLogDest: "127.0.0.1:5561",
84+
AllowList: []v1beta1.AllowListEntry{
85+
{IPWithMask: "192.168.1.0/24"},
86+
{IPWithMask: "10.0.0.0/8"},
87+
},
88+
},
89+
}
7190

7291
tests := []struct {
7392
dosProtectedEx *DosEx
@@ -127,6 +146,21 @@ func TestUpdateApDosResource(t *testing.T) {
127146
},
128147
msg: "app protect dos policy and log conf",
129148
},
149+
{
150+
dosProtectedEx: &DosEx{
151+
DosProtected: appProtectDosProtectedWithAllowList,
152+
DosPolicy: appProtectDosPolicy,
153+
},
154+
expected: &appProtectDosResource{
155+
AppProtectDosEnable: "on",
156+
AppProtectDosName: "test-ns/dosWithAllowList/dos-protected",
157+
AppProtectDosMonitorURI: "example.com",
158+
AppProtectDosAccessLogDst: "syslog:server=127.0.0.1:5561",
159+
AppProtectDosPolicyFile: "/etc/nginx/dos/policies/test-ns_test-name.json",
160+
AppProtectDosAllowListPath: "/etc/nginx/dos/allowlist/test-ns_dosWithAllowList.json",
161+
},
162+
msg: "app protect dos with allow list",
163+
},
130164
}
131165

132166
for _, test := range tests {

0 commit comments

Comments
 (0)