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

[RS-2246] Updates for view WAF ruleset epic #3664

Merged
merged 13 commits into from
Dec 20, 2024
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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 @@ -119,6 +120,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/operator-framework/operator-sdk => github.com/operator-framework/operator-sdk v1.0.1
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 @@ -135,6 +139,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
Loading