Skip to content

Commit

Permalink
Merge pull request #3664 from gantony/antony-no-coraza-default-ruleset
Browse files Browse the repository at this point in the history
[RS-2246] Updates for view WAF ruleset epic
  • Loading branch information
Brian-McM authored Dec 20, 2024
2 parents d12f316 + d40aa29 commit 09433f9
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 213 deletions.
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/corazawaf/coraza-coreruleset/v4 v4.7.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/elastic/go-sysinfo v1.13.1 // indirect
github.com/elastic/go-ucfg v0.8.8 // indirect
Expand Down Expand Up @@ -121,6 +122,11 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

require (
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc // indirect
github.com/magefile/mage v1.14.0 // indirect
)

replace (
github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.2+incompatible // Required by OLM
github.com/imdario/mergo => github.com/imdario/mergo v0.3.16 // Per advice at https://github.com/darccio/mergo?tab=readme-ov-file#100
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ github.com/cloudflare/cfssl v1.6.5 h1:46zpNkm6dlNkMZH/wMW22ejih6gIaJbzL2du6vD7Ze
github.com/cloudflare/cfssl v1.6.5/go.mod h1:Bk1si7sq8h2+yVEDrFJiz3d7Aw+pfjjJSZVaD+Taky4=
github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM=
github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M=
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc h1:OlJhrgI3I+FLUCTI3JJW8MoqyM78WbqJjecqMnqG+wc=
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc/go.mod h1:7rsocqNDkTCira5T0M7buoKR2ehh7YZiPkzxRuAgvVU=
github.com/corazawaf/coraza-coreruleset/v4 v4.7.0 h1:j02CDxQYHVFZfBxbKLWYg66jSLbPmZp1GebyMwzN9Z0=
github.com/corazawaf/coraza-coreruleset/v4 v4.7.0/go.mod h1:1FQt1p+JSQ6tYrafMqZrEEdDmhq6aVuIJdnk+bM9hMY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -141,6 +145,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
Expand Down
106 changes: 43 additions & 63 deletions pkg/controller/applicationlayer/applicationlayer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"github.com/tigera/operator/pkg/ctrlruntime"
"github.com/tigera/operator/pkg/render"
"github.com/tigera/operator/pkg/render/applicationlayer"
"github.com/tigera/operator/pkg/render/applicationlayer/embed"
"github.com/tigera/operator/pkg/render/applicationlayer/ruleset"
rmeta "github.com/tigera/operator/pkg/render/common/meta"

admregv1 "k8s.io/api/admissionregistration/v1"
Expand Down Expand Up @@ -115,12 +115,12 @@ func add(mgr manager.Manager, c ctrlruntime.Controller) error {
return fmt.Errorf("applicationlayer-controller failed to watch Tigera network resource: %v", err)
}

// Watch for configmap changes in tigera-operator namespace; the cm contains ruleset for ModSecurity library:
err = utils.AddConfigMapWatch(c, applicationlayer.ModSecurityRulesetConfigMapName, common.OperatorNamespace(), &handler.EnqueueRequestForObject{})
// Watch for configmap changes in tigera-operator namespace; the cm contains config for Coraza library:
err = utils.AddConfigMapWatch(c, applicationlayer.WAFRulesetConfigMapName, common.OperatorNamespace(), &handler.EnqueueRequestForObject{})
if err != nil {
return fmt.Errorf(
"applicationlayer-controller failed to watch ConfigMap %s: %v",
applicationlayer.ModSecurityRulesetConfigMapName, err,
applicationlayer.WAFRulesetConfigMapName, err,
)
}

Expand All @@ -134,7 +134,8 @@ func add(mgr manager.Manager, c ctrlruntime.Controller) error {
// Watch configmaps created for envoy and dikastes in calico-system namespace:
maps := []string{
applicationlayer.EnvoyConfigMapName,
applicationlayer.ModSecurityRulesetConfigMapName,
applicationlayer.WAFRulesetConfigMapName,
applicationlayer.DefaultCoreRuleset,
}
for _, configMapName := range maps {
if err = utils.AddConfigMapWatch(c, configMapName, common.CalicoNamespace, &handler.EnqueueRequestForObject{}); err != nil {
Expand Down Expand Up @@ -253,34 +254,40 @@ func (r *ReconcileApplicationLayer) Reconcile(ctx context.Context, request recon
return reconcile.Result{}, err
}

var passthroughModSecurityRuleSet bool
var modSecurityRuleSet *corev1.ConfigMap
var passthroughWAFRulesetConfig bool
var wafRulesetConfig, defaultCoreRuleSet *corev1.ConfigMap
if r.isWAFEnabled(&instance.Spec) || r.isSidecarInjectionEnabled(&instance.Spec) {
if modSecurityRuleSet, passthroughModSecurityRuleSet, err = r.getModSecurityRuleSet(ctx); err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error getting Web Application Firewall ModSecurity rule set", err, reqLogger)
if defaultCoreRuleSet, err = ruleset.GetOWASPCoreRuleSet(); err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error getting Web Application Firewall OWASP core ruleset", err, reqLogger)
return reconcile.Result{}, err
}
if err = validateModSecurityRuleSet(modSecurityRuleSet); err != nil {
r.status.SetDegraded(operatorv1.ResourceValidationError, "Error validating Web Application Firewall rule set", err, reqLogger)

if wafRulesetConfig, passthroughWAFRulesetConfig, err = r.getWAFRulesetConfig(ctx); err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error getting Web Application Firewall ruleset config", err, reqLogger)
return reconcile.Result{}, err
}
if err = ruleset.ValidateWAFRulesetConfig(wafRulesetConfig); err != nil {
r.status.SetDegraded(operatorv1.ResourceValidationError, "Error validating Web Application Firewall ruleset config", err, reqLogger)
return reconcile.Result{}, err
}
}

lcSpec := instance.Spec.LogCollection
config := &applicationlayer.Config{
PullSecrets: pullSecrets,
Installation: installation,
OsType: rmeta.OSTypeLinux,
PerHostWAFEnabled: r.isWAFEnabled(&instance.Spec),
PerHostLogsEnabled: r.isLogsCollectionEnabled(&instance.Spec),
PerHostALPEnabled: r.isALPEnabled(&instance.Spec),
SidecarInjectionEnabled: r.isSidecarInjectionEnabled(&instance.Spec),
LogRequestsPerInterval: lcSpec.LogRequestsPerInterval,
LogIntervalSeconds: lcSpec.LogIntervalSeconds,
ModSecurityConfigMap: modSecurityRuleSet,
UseRemoteAddressXFF: instance.Spec.EnvoySettings.UseRemoteAddress,
NumTrustedHopsXFF: instance.Spec.EnvoySettings.XFFNumTrustedHops,
ApplicationLayer: instance,
PullSecrets: pullSecrets,
Installation: installation,
OsType: rmeta.OSTypeLinux,
PerHostWAFEnabled: r.isWAFEnabled(&instance.Spec),
PerHostLogsEnabled: r.isLogsCollectionEnabled(&instance.Spec),
PerHostALPEnabled: r.isALPEnabled(&instance.Spec),
SidecarInjectionEnabled: r.isSidecarInjectionEnabled(&instance.Spec),
LogRequestsPerInterval: lcSpec.LogRequestsPerInterval,
LogIntervalSeconds: lcSpec.LogIntervalSeconds,
WAFRulesetConfigMap: wafRulesetConfig,
DefaultCoreRulesetConfigMap: defaultCoreRuleSet,
UseRemoteAddressXFF: instance.Spec.EnvoySettings.UseRemoteAddress,
NumTrustedHopsXFF: instance.Spec.EnvoySettings.XFFNumTrustedHops,
ApplicationLayer: instance,
}
component := applicationlayer.ApplicationLayer(config)

Expand All @@ -291,8 +298,8 @@ func (r *ReconcileApplicationLayer) Reconcile(ctx context.Context, request recon
return reconcile.Result{}, err
}

if passthroughModSecurityRuleSet {
err = ch.CreateOrUpdateOrDelete(ctx, render.NewPassthrough(modSecurityRuleSet), r.status)
if passthroughWAFRulesetConfig {
err = ch.CreateOrUpdateOrDelete(ctx, render.NewPassthrough(wafRulesetConfig), r.status)
if err != nil {
r.status.SetDegraded(operatorv1.ResourceUpdateError, "Error creating / updating resource", err, reqLogger)
return reconcile.Result{}, err
Expand Down Expand Up @@ -419,57 +426,30 @@ func validateApplicationLayer(al *operatorv1.ApplicationLayer) error {
return nil
}

// getModSecurityRuleSet returns 'owasp-ruleset-config' ConfigMap from calico-operator namespace.
// The ConfigMap is meant to contain rule set files for ModSecurity library.
// If the ConfigMap does not exist a ConfigMap with OWASP provided Core Rule Set will be returned.
// The rule set was cloned from https://github.com/coreruleset/coreruleset/
func (r *ReconcileApplicationLayer) getModSecurityRuleSet(ctx context.Context) (*corev1.ConfigMap, bool, error) {
ruleset := new(corev1.ConfigMap)
// getWAFRulesetConfig returns 'tigera-coreruleset-config' ConfigMap from calico-operator namespace.
// The ConfigMap is meant to contain the configuration for the Coraza library.
// If the ConfigMap does not exist a ConfigMap with the Tigera ruleset config will be returned.
func (r *ReconcileApplicationLayer) getWAFRulesetConfig(ctx context.Context) (*corev1.ConfigMap, bool, error) {
cm := new(corev1.ConfigMap)

if err := r.client.Get(
ctx,
types.NamespacedName{
Namespace: common.OperatorNamespace(),
Name: applicationlayer.ModSecurityRulesetConfigMapName,
Name: applicationlayer.WAFRulesetConfigMapName,
},
ruleset,
cm,
); err == nil {
return ruleset, false, nil
return cm, false, nil
} else if !apierrors.IsNotFound(err) {
return nil, false, err
}

ruleset, err := getDefaultCoreRuleset(ctx)
cm, err := ruleset.GetWAFRulesetConfig()
if err != nil {
return nil, false, err
}
return ruleset, true, nil
}

func getDefaultCoreRuleset(ctx context.Context) (*corev1.ConfigMap, error) {
ruleset, err := embed.AsConfigMap(
applicationlayer.ModSecurityRulesetConfigMapName,
common.OperatorNamespace(),
)
if err != nil {
return nil, err
}

return ruleset, nil
}

func validateModSecurityRuleSet(cm *corev1.ConfigMap) error {
requiredFiles := []string{
"tigera.conf",
}

for _, f := range requiredFiles {
if _, ok := cm.Data[f]; !ok {
return fmt.Errorf("file must be present with ruleset files: %s", f)
}
}

return nil
return cm, true, nil
}

// getApplicationLayer returns the default ApplicationLayer instance.
Expand Down
104 changes: 70 additions & 34 deletions pkg/render/applicationlayer/applicationlayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,27 @@ import (
)

const (
APLName = "application-layer"
RoleName = "application-layer"
ApplicationLayerDaemonsetName = "l7-log-collector"
L7CollectorContainerName = "l7-collector"
L7CollectorSocksVolumeName = "l7-collector-socks"
ProxyContainerName = "envoy-proxy"
EnvoyLogsVolumeName = "envoy-logs"
EnvoyConfigMapName = "envoy-config"
EnvoyConfigMapKey = "envoy-config.yaml"
FelixSync = "felix-sync"
DikastesSyncVolumeName = "dikastes-sync"
DikastesContainerName = "dikastes"
ModSecurityRulesetVolumeName = "modsecurity-ruleset"
ModSecurityRulesetVolumePath = "/etc/modsecurity-ruleset"
ModSecurityRulesetConfigMapName = "modsecurity-ruleset"
ModSecurityRulesetHashAnnotation = "hash.operator.tigera.io/modsecurity-ruleset"
CalicoLogsVolumeName = "var-log-calico"
CalicologsVolumePath = "/var/log/calico"
APLName = "application-layer"
RoleName = "application-layer"
ApplicationLayerDaemonsetName = "l7-log-collector"
L7CollectorContainerName = "l7-collector"
L7CollectorSocksVolumeName = "l7-collector-socks"
ProxyContainerName = "envoy-proxy"
EnvoyLogsVolumeName = "envoy-logs"
EnvoyConfigMapName = "envoy-config"
EnvoyConfigMapKey = "envoy-config.yaml"
FelixSync = "felix-sync"
DikastesSyncVolumeName = "dikastes-sync"
DikastesContainerName = "dikastes"
WAFConfigVolumeName = "tigera-waf-config"
WAFConfigVolumePath = "/etc/waf"
DefaultCoreRulesetVolumeName = "coreruleset-default"
DefaultCoreRulesetVolumePath = "/etc/waf/coreruleset"
WAFRulesetConfigMapName = "tigera-waf-config"
DefaultCoreRuleset = "coreruleset-default"
WAFConfigHashAnnotation = "hash.operator.tigera.io/tigera-waf-config"
CalicoLogsVolumeName = "var-log-calico"
CalicologsVolumePath = "/var/log/calico"
)

func ApplicationLayer(
Expand All @@ -82,8 +85,9 @@ type Config struct {
OsType rmeta.OSType

// Optional config for WAF.
PerHostWAFEnabled bool
ModSecurityConfigMap *corev1.ConfigMap
PerHostWAFEnabled bool
WAFRulesetConfigMap *corev1.ConfigMap
DefaultCoreRulesetConfigMap *corev1.ConfigMap

// Optional config for L7 logs.
PerHostLogsEnabled bool
Expand Down Expand Up @@ -169,7 +173,8 @@ func (c *component) Objects() ([]client.Object, []client.Object) {
// If Web Application Firewall or Sidecar Injection is enabled, we need WAF ruleset ConfigMap present.
if c.config.PerHostWAFEnabled || c.config.SidecarInjectionEnabled {
// this ConfigMap is a copy of the provided configuration from the operator namespace into the calico-system namespace
objs = append(objs, c.modSecurityConfigMap())
objs = append(objs, c.wafRulesetConfigMap())
objs = append(objs, c.defaultCoreRulesetConfigMap())
}

// Envoy configuration
Expand Down Expand Up @@ -211,8 +216,8 @@ func (c *component) daemonset() *appsv1.DaemonSet {
annots[EnvoyConfigMapName] = rmeta.AnnotationHash(c.config.envoyConfigMap)
}

if c.config.ModSecurityConfigMap != nil {
annots[ModSecurityRulesetHashAnnotation] = rmeta.AnnotationHash(c.config.ModSecurityConfigMap.Data)
if c.config.WAFRulesetConfigMap != nil {
annots[WAFConfigHashAnnotation] = rmeta.AnnotationHash(c.config.WAFRulesetConfigMap.Data)
}

podTemplate := corev1.PodTemplateSpec{
Expand Down Expand Up @@ -313,7 +318,8 @@ func (c *component) containers() []corev1.Container {
commandArgs = append(
commandArgs,
"--waf-log-file", filepath.Join(CalicologsVolumePath, "waf", "waf.log"),
"--waf-ruleset-file", filepath.Join(ModSecurityRulesetVolumePath, "tigera.conf"),
"--waf-ruleset-root-dir", WAFConfigVolumePath,
"--waf-ruleset-file", "tigera.conf",
)
if c.config.PerHostWAFEnabled {
commandArgs = append(commandArgs, "--per-host-waf-enabled")
Expand All @@ -326,8 +332,13 @@ func (c *component) containers() []corev1.Container {
MountPath: CalicologsVolumePath,
},
{
Name: ModSecurityRulesetVolumeName,
MountPath: ModSecurityRulesetVolumePath,
Name: WAFConfigVolumeName,
MountPath: WAFConfigVolumePath,
ReadOnly: true,
},
{
Name: DefaultCoreRulesetVolumeName,
MountPath: DefaultCoreRulesetVolumePath,
ReadOnly: true,
},
}...,
Expand Down Expand Up @@ -446,7 +457,7 @@ func (c *component) volumes() []corev1.Volume {
},
})

// Needed for ModSecurity library - contains rule set.
// Needed for Coraza library - contains rule set.
if c.config.PerHostWAFEnabled || c.config.SidecarInjectionEnabled {
// WAF logs need HostPath volume - logs to be consumed by fluentd.
volumes = append(volumes, corev1.Volume{
Expand All @@ -459,13 +470,25 @@ func (c *component) volumes() []corev1.Volume {
},
})

// WAF modsecurity ruleset volume
// WAF ruleset volume
volumes = append(volumes, corev1.Volume{
Name: ModSecurityRulesetVolumeName,
Name: WAFConfigVolumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: ModSecurityRulesetConfigMapName,
Name: WAFRulesetConfigMapName,
},
},
},
})

// Default coreruleset volume
volumes = append(volumes, corev1.Volume{
Name: DefaultCoreRulesetVolumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: DefaultCoreRuleset,
},
},
},
Expand Down Expand Up @@ -502,16 +525,29 @@ func (c *component) collectorVolMounts() []corev1.VolumeMount {
}
}

func (c *component) modSecurityConfigMap() *corev1.ConfigMap {
func (c *component) wafRulesetConfigMap() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{
Name: WAFRulesetConfigMapName,
Namespace: common.CalicoNamespace,
Labels: map[string]string{},
},
Data: c.config.WAFRulesetConfigMap.Data,
BinaryData: c.config.WAFRulesetConfigMap.BinaryData,
}
}

func (c *component) defaultCoreRulesetConfigMap() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{
Name: ModSecurityRulesetConfigMapName,
Name: DefaultCoreRuleset,
Namespace: common.CalicoNamespace,
Labels: map[string]string{},
},
Data: c.config.ModSecurityConfigMap.Data,
BinaryData: c.config.ModSecurityConfigMap.BinaryData,
Data: c.config.DefaultCoreRulesetConfigMap.Data,
BinaryData: c.config.DefaultCoreRulesetConfigMap.BinaryData,
}
}

Expand Down
Loading

0 comments on commit 09433f9

Please sign in to comment.