Skip to content

Commit

Permalink
Add support for apLogBundle in WAF policy (#5259)
Browse files Browse the repository at this point in the history
* add support for apLogBundle
  • Loading branch information
oseoin authored Mar 15, 2024
1 parent 8aba4ea commit 323cce7
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 95 deletions.
4 changes: 4 additions & 0 deletions config/crd/bases/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ spec:
securityLog:
description: SecurityLog defines the security log of a WAF policy.
properties:
apLogBundle:
type: string
apLogConf:
type: string
enable:
Expand All @@ -198,6 +200,8 @@ spec:
items:
description: SecurityLog defines the security log of a WAF policy.
properties:
apLogBundle:
type: string
apLogConf:
type: string
enable:
Expand Down
4 changes: 4 additions & 0 deletions deploy/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ spec:
securityLog:
description: SecurityLog defines the security log of a WAF policy.
properties:
apLogBundle:
type: string
apLogConf:
type: string
enable:
Expand All @@ -400,6 +402,8 @@ spec:
items:
description: SecurityLog defines the security log of a WAF policy.
properties:
apLogBundle:
type: string
apLogConf:
type: string
enable:
Expand Down
6 changes: 4 additions & 2 deletions docs/content/configuration/policy-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,9 +547,11 @@ waf:
|Field | Description | Type | Required |
| ---| ---| ---| --- |
|``enable`` | Enables NGINX App Protect WAF. | ``bool`` | Yes |
|``apPolicy`` | The [App Protect WAF policy](/nginx-ingress-controller/app-protect/configuration/#app-protect-policies) of the WAF. Accepts an optional namespace. | ``string`` | No |
|``apPolicy`` | The [App Protect WAF policy]({{< relref "installation/integrations/app-protect-waf/configuration.md#waf-policies" >}}) of the WAF. Accepts an optional namespace. Mutually exclusive with ``apBundle``. | ``string`` | No |
|``apBundle`` | The [App Protect WAF policy bundle]({{< relref "installation/integrations/app-protect-waf/configuration.md#waf-bundles" >}}). Mutually exclusive with ``apPolicy``. | ``string`` | No |
|``securityLog.enable`` | Enables security log. | ``bool`` | No |
|``securityLog.apLogConf`` | The [App Protect WAF log conf](/nginx-ingress-controller/app-protect/configuration/#app-protect-logs) resource. Accepts an optional namespace. | ``string`` | No |
|``securityLog.apLogConf`` | The [App Protect WAF log conf]({{< relref "installation/integrations/app-protect-waf/configuration.md#waf-logs" >}}) resource. Accepts an optional namespace. Only works with ``apPolicy``. | ``string`` | No |
|``securityLog.apLogBundle`` | The [App Protect WAF log bundle]({{< relref "installation/integrations/app-protect-waf/configuration.md#waf-bundles" >}}) resource. Only works with ``apBundle``. | ``string`` | No |
|``securityLog.logDest`` | The log destination for the security log. Accepted variables are ``syslog:server=<ip-address &#124; localhost; fqdn>:<port>``, ``stderr``, ``<absolute path to file>``. Default is ``"syslog:server=127.0.0.1:514"``. | ``string`` | No |
{{% /table %}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ NGINX App Protect WAF can be enabled and configured for custom resources (Virtua
- For Ingress resources, apply the [`app-protect` annotations]({{< relref "configuration/ingress-resources/advanced-configuration-with-annotations.md#app-protect" >}}) to each desired resource.


## NGINX App Protect WAF Policies
## NGINX App Protect WAF Policies {#waf-policies}

NGINX App Protect WAF Policies can be created for VirtualServer, VirtualServerRoute, or Ingress resources by creating an `APPolicy` [custom resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). There are some caveats:

Expand Down Expand Up @@ -99,7 +99,7 @@ spec:
Notice that the fields match in name and nesting: NGINX Ingress Controller will transform the YAML into a valid JSON WAF policy config.
## NGINX App Protect WAF Logs
## NGINX App Protect WAF Logs {#waf-logs}
Configuring
Expand Down Expand Up @@ -206,15 +206,15 @@ spec:
tag: Fruits
```

## App Protect WAF Bundles
## NGINX App Protect WAF Bundles {#waf-bundles}

You can define App Protect WAF bundles for VirtualServer custom resources by creating policy bundles and putting them on a mounted volume accessible from NGINX Ingress Controller.

Before applying a policy, a WAF policy bundle must be created, then copied to a volume mounted to `/etc/nginx/waf/bundles`.

{{< note >}} NGINX Ingress Controller does not currently support `securityLogs` for policy bundles. {{< /note >}}
{{< note >}} NGINX Ingress Controller supports `securityLogs` for policy bundles when using `apLogBundle` instead of `apLogConf`. Log bundles must also be copied to a volume mounted to `/etc/nginx/waf/bundles`. {{< /note >}}

This example show how a policy is configured by referencing a generated WAF Policy Bundle:
This example shows how a policy is configured by referencing a generated WAF Policy Bundle:


```yaml
Expand All @@ -228,6 +228,24 @@ spec:
apBundle: "<policy_bundle_name>.tgz"
```

This example shows the same policy as above but with a log bundle used for :


```yaml
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: <policy_name>
spec:
waf:
enable: true
apBundle: "<policy_bundle_name>.tgz"
securityLogs:
- enable: true
apLogBundle: "<log_bundle_name>.tgz"
logDest: "syslog:server=syslog-svc.default:514"
```

## OpenAPI Specification in NGINX Ingress Controller

The OpenAPI Specification defines the spec file format needed to describe RESTful APIs. The spec file can be written either in JSON or YAML. Using a spec file simplifies the work of implementing API protection. Refer to the [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification) (formerly called Swagger) for details.
Expand Down
2 changes: 1 addition & 1 deletion internal/configs/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ func (cnf *Configurator) addOrUpdateVirtualServer(virtualServerEx *VirtualServer

name := getFileNameForVirtualServer(virtualServerEx.VirtualServer)

vsc := newVirtualServerConfigurator(cnf.cfgParams, cnf.isPlus, cnf.IsResolverConfigured(), cnf.staticCfgParams, cnf.isWildcardEnabled)
vsc := newVirtualServerConfigurator(cnf.cfgParams, cnf.isPlus, cnf.IsResolverConfigured(), cnf.staticCfgParams, cnf.isWildcardEnabled, nil)
vsCfg, warnings := vsc.GenerateVirtualServerConfig(virtualServerEx, apResources, dosResources)
content, err := cnf.templateExecutorV2.ExecuteVirtualServerTemplate(&vsCfg)
if err != nil {
Expand Down
97 changes: 67 additions & 30 deletions internal/configs/virtualserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package configs
import (
"fmt"
"net/url"
"os"
"path"
"sort"
"strconv"
"strings"
Expand All @@ -25,6 +27,7 @@ const (
specContext = "spec"
routeContext = "route"
subRouteContext = "subroute"
defaultLogOutput = "syslog:server=localhost:514"
)

var grpcConflictingErrors = map[int]bool{
Expand Down Expand Up @@ -240,6 +243,7 @@ type virtualServerConfigurator struct {
isIPV6Disabled bool
DynamicSSLReloadEnabled bool
StaticSSLPath string
bundleValidator bundleValidator
}

type oidcPolicyCfg struct {
Expand Down Expand Up @@ -268,7 +272,11 @@ func newVirtualServerConfigurator(
isResolverConfigured bool,
staticParams *StaticConfigParams,
isWildcardEnabled bool,
bundleValidator bundleValidator,
) *virtualServerConfigurator {
if bundleValidator == nil {
bundleValidator = newInternalBundleValidator(appProtectBundleFolder)
}
return &virtualServerConfigurator{
cfgParams: cfgParams,
isPlus: isPlus,
Expand All @@ -283,6 +291,7 @@ func newVirtualServerConfigurator(
isIPV6Disabled: staticParams.DisableIPV6,
DynamicSSLReloadEnabled: staticParams.DynamicSSLReload,
StaticSSLPath: staticParams.StaticSSLPath,
bundleValidator: bundleValidator,
}
}

Expand Down Expand Up @@ -789,10 +798,34 @@ type policiesCfg struct {
OIDC bool
WAF *version2.WAF
ErrorReturn *version2.Return
BundleValidator bundleValidator
}

type bundleValidator interface {
// validate returns the full path to the bundle and an error if the file is not accessible
validate(string) (string, error)
}

type internalBundleValidator struct {
bundlePath string
}

func (i internalBundleValidator) validate(bundle string) (string, error) {
bundle = path.Join(i.bundlePath, bundle)
_, err := os.Stat(bundle)
return bundle, err
}

func newPoliciesConfig() *policiesCfg {
return &policiesCfg{}
func newInternalBundleValidator(b string) internalBundleValidator {
return internalBundleValidator{
bundlePath: b,
}
}

func newPoliciesConfig(bv bundleValidator) *policiesCfg {
return &policiesCfg{
BundleValidator: bv,
}
}

type policyOwnerDetails struct {
Expand Down Expand Up @@ -1229,42 +1262,46 @@ func (p *policiesCfg) addWAFConfig(

if waf.ApBundle != "" {
p.WAF.ApBundle = appProtectBundleFolder + waf.ApBundle
bundlePath, err := p.BundleValidator.validate(waf.ApBundle)
if err != nil {
res.addWarningf("WAF policy %s references an invalid or non-existing App Protect bundle %s", polKey, bundlePath)
res.isError = true
}
p.WAF.ApBundle = bundlePath
}

if waf.SecurityLog != nil && waf.SecurityLogs == nil {
glog.V(2).Info("the field securityLog is deprecated nad will be removed in future releases. Use field securityLogs instead")
p.WAF.ApSecurityLogEnable = true

logConfKey := waf.SecurityLog.ApLogConf
hasNamespace := strings.Contains(logConfKey, "/")
if !hasNamespace {
logConfKey = fmt.Sprintf("%v/%v", polNamespace, logConfKey)
}

if logConfPath, ok := apResources.LogConfs[logConfKey]; ok {
logDest := generateString(waf.SecurityLog.LogDest, "syslog:server=localhost:514")
p.WAF.ApLogConf = []string{fmt.Sprintf("%s %s", logConfPath, logDest)}
} else {
res.addWarningf("WAF policy %s references an invalid or non-existing log config %s", polKey, logConfKey)
res.isError = true
}
glog.V(2).Info("the field securityLog is deprecated and will be removed in future releases. Use field securityLogs instead")
waf.SecurityLogs = append(waf.SecurityLogs, waf.SecurityLog)
}

if waf.SecurityLogs != nil {
p.WAF.ApSecurityLogEnable = true
p.WAF.ApLogConf = []string{}
for _, loco := range waf.SecurityLogs {
logConfKey := loco.ApLogConf
hasNamepace := strings.Contains(logConfKey, "/")
if !hasNamepace {
logConfKey = fmt.Sprintf("%v/%v", polNamespace, logConfKey)
logDest := generateString(loco.LogDest, defaultLogOutput)

if loco.ApLogConf != "" {
logConfKey := loco.ApLogConf
if !strings.Contains(logConfKey, "/") {
logConfKey = fmt.Sprintf("%v/%v", polNamespace, logConfKey)
}
if logConfPath, ok := apResources.LogConfs[logConfKey]; ok {
p.WAF.ApLogConf = append(p.WAF.ApLogConf, fmt.Sprintf("%s %s", logConfPath, logDest))
} else {
res.addWarningf("WAF policy %s references an invalid or non-existing log config %s", polKey, logConfKey)
res.isError = true
}
}
if logConfPath, ok := apResources.LogConfs[logConfKey]; ok {
logDest := generateString(loco.LogDest, "syslog:server=localhost:514")
p.WAF.ApLogConf = append(p.WAF.ApLogConf, fmt.Sprintf("%s %s", logConfPath, logDest))
} else {
res.addWarningf("WAF policy %s references an invalid or non-existing log config %s", polKey, logConfKey)
res.isError = true

if loco.ApLogBundle != "" {
logBundle, err := p.BundleValidator.validate(loco.ApLogBundle)
if err != nil {
res.addWarningf("WAF policy %s references an invalid or non-existing log config bundle %s", polKey, logBundle)
res.isError = true
} else {
p.WAF.ApLogConf = append(p.WAF.ApLogConf, fmt.Sprintf("%s %s", logBundle, logDest))
}
}
}
}
Expand All @@ -1278,7 +1315,7 @@ func (vsc *virtualServerConfigurator) generatePolicies(
context string,
policyOpts policyOptions,
) policiesCfg {
config := newPoliciesConfig()
config := newPoliciesConfig(vsc.bundleValidator)

for _, p := range policyRefs {
polNamespace := p.Namespace
Expand Down Expand Up @@ -2426,7 +2463,7 @@ func createUpstreamsForPlus(

isPlus := true
upstreamNamer := NewUpstreamNamerForVirtualServer(virtualServerEx.VirtualServer)
vsc := newVirtualServerConfigurator(baseCfgParams, isPlus, false, staticParams, false)
vsc := newVirtualServerConfigurator(baseCfgParams, isPlus, false, staticParams, false, nil)

for _, u := range virtualServerEx.VirtualServer.Spec.Upstreams {
isExternalNameSvc := virtualServerEx.ExternalNameSvcs[GenerateExternalNameSvcKey(virtualServerEx.VirtualServer.Namespace, u.Service)]
Expand Down
Loading

0 comments on commit 323cce7

Please sign in to comment.