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

Add support for apLogBundle in WAF policy #5259

Merged
merged 11 commits into from
Mar 15, 2024
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 :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oseoin - is there a missing word here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea how I missed this, thanks @jputrino! Fixed in #5269



```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
Loading