Skip to content
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

Dos - Allow list #5824

Merged
merged 22 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/scripts/nap-dos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions deploy/crds-nap-dos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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=<ip-address &#124; localhost &#124; dns-name>:<port>``, ``stderr``, ``<absolute path to file>``. 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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"
57 changes: 57 additions & 0 deletions internal/configs/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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()))
Expand All @@ -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
Expand Down
63 changes: 63 additions & 0 deletions internal/configs/configurator_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package configs

import (
"encoding/json"
"os"
"reflect"
"testing"
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
})
}
}
5 changes: 5 additions & 0 deletions internal/configs/dos.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type appProtectDosResource struct {
AppProtectDosAccessLogDst string
AppProtectDosPolicyFile string
AppProtectDosLogConfFile string
AppProtectDosAllowListPath string
}

func getAppProtectDosResource(dosEx *DosEx) *appProtectDosResource {
Expand All @@ -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
Expand Down
34 changes: 34 additions & 0 deletions internal/configs/dos_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions internal/configs/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 18 additions & 14 deletions internal/configs/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions internal/configs/version1/__snapshots__/template_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions internal/configs/version1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ type Server struct {
AppProtectDosMonitorProtocol string
AppProtectDosMonitorTimeout uint64
AppProtectDosName string
AppProtectDosAllowListPath string
AppProtectDosAccessLogDst string

SpiffeCerts bool
Expand Down
Loading
Loading