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] Add waf default core ruleset as configmap #3649

Closed
wants to merge 4 commits into from
Closed
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
3 changes: 3 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,8 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

require 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
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ 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/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 +137,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
64 changes: 50 additions & 14 deletions pkg/controller/applicationlayer/applicationlayer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"io/fs"

operatorv1 "github.com/tigera/operator/api/v1"
crdv1 "github.com/tigera/operator/pkg/apis/crd.projectcalico.org/v1"
Expand Down Expand Up @@ -45,6 +46,8 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

coreruleset "github.com/corazawaf/coraza-coreruleset/v4"
)

const ResourceName = "applicationlayer"
Expand Down Expand Up @@ -124,6 +127,14 @@ func add(mgr manager.Manager, c ctrlruntime.Controller) error {
)
}

err = utils.AddConfigMapWatch(c, applicationlayer.DefaultCoreRuleset, common.OperatorNamespace(), &handler.EnqueueRequestForObject{})
if err != nil {
return fmt.Errorf(
"applicationlayer-controller failed to watch ConfigMap %s: %v",
applicationlayer.DefaultCoreRuleset, err,
)
}

// Watch mutatingwebhookconfiguration responsible for sidecar injetion
err = c.WatchObject(&admregv1.MutatingWebhookConfiguration{ObjectMeta: metav1.ObjectMeta{Name: common.SidecarMutatingWebhookConfigName}},
&handler.EnqueueRequestForObject{})
Expand All @@ -135,6 +146,7 @@ func add(mgr manager.Manager, c ctrlruntime.Controller) error {
maps := []string{
applicationlayer.EnvoyConfigMapName,
applicationlayer.ModSecurityRulesetConfigMapName,
applicationlayer.DefaultCoreRuleset,
}
for _, configMapName := range maps {
if err = utils.AddConfigMapWatch(c, configMapName, common.CalicoNamespace, &handler.EnqueueRequestForObject{}); err != nil {
Expand Down Expand Up @@ -254,8 +266,13 @@ func (r *ReconcileApplicationLayer) Reconcile(ctx context.Context, request recon
}

var passthroughModSecurityRuleSet bool
var modSecurityRuleSet *corev1.ConfigMap
var modSecurityRuleSet, owaspCoreRuleSet *corev1.ConfigMap
if r.isWAFEnabled(&instance.Spec) || r.isSidecarInjectionEnabled(&instance.Spec) {
if owaspCoreRuleSet, err = getOWASPCoreRuleSet(ctx); err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error getting Web Application Firewall OWASP core rule set", err, reqLogger)
return reconcile.Result{}, err
}

if modSecurityRuleSet, passthroughModSecurityRuleSet, err = r.getModSecurityRuleSet(ctx); err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error getting Web Application Firewall ModSecurity rule set", err, reqLogger)
return reconcile.Result{}, err
Expand All @@ -268,19 +285,20 @@ func (r *ReconcileApplicationLayer) Reconcile(ctx context.Context, request recon

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,
ModSecurityConfigMap: modSecurityRuleSet,
DefaultCoreRulesetConfigMap: owaspCoreRuleSet,
UseRemoteAddressXFF: instance.Spec.EnvoySettings.UseRemoteAddress,
NumTrustedHopsXFF: instance.Spec.EnvoySettings.XFFNumTrustedHops,
ApplicationLayer: instance,
}
component := applicationlayer.ApplicationLayer(config)

Expand Down Expand Up @@ -450,6 +468,24 @@ func getDefaultCoreRuleset(ctx context.Context) (*corev1.ConfigMap, error) {
ruleset, err := embed.AsConfigMap(
applicationlayer.ModSecurityRulesetConfigMapName,
common.OperatorNamespace(),
embed.FS,
)
if err != nil {
return nil, err
}

return ruleset, nil
}

func getOWASPCoreRuleSet(ctx context.Context) (*corev1.ConfigMap, error) {
owaspCRS, err := fs.Sub(coreruleset.FS, "@owasp_crs")
if err != nil {
return nil, err
}
ruleset, err := embed.AsConfigMap(
applicationlayer.DefaultCoreRuleset,
common.OperatorNamespace(),
owaspCRS,
)
if err != nil {
return nil, err
Expand Down
38 changes: 36 additions & 2 deletions pkg/render/applicationlayer/applicationlayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ const (
DikastesContainerName = "dikastes"
ModSecurityRulesetVolumeName = "modsecurity-ruleset"
ModSecurityRulesetVolumePath = "/etc/modsecurity-ruleset"
DefaultCoreRulesetVolumeName = "coreruleset-default"
DefaultCoreRulesetVolumePath = "/etc/modsecurity-ruleset/coreruleset-default"
ModSecurityRulesetConfigMapName = "modsecurity-ruleset"
DefaultCoreRuleset = "coreruleset-default"
ModSecurityRulesetHashAnnotation = "hash.operator.tigera.io/modsecurity-ruleset"
CalicoLogsVolumeName = "var-log-calico"
CalicologsVolumePath = "/var/log/calico"
Expand All @@ -82,8 +85,9 @@ type Config struct {
OsType rmeta.OSType

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

// Optional config for L7 logs.
PerHostLogsEnabled bool
Expand Down Expand Up @@ -170,6 +174,7 @@ func (c *component) Objects() ([]client.Object, []client.Object) {
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.defaultCoreRulesetConfigMap())
}

// Envoy configuration
Expand Down Expand Up @@ -330,6 +335,11 @@ func (c *component) containers() []corev1.Container {
MountPath: ModSecurityRulesetVolumePath,
ReadOnly: true,
},
{
Name: DefaultCoreRulesetVolumeName,
MountPath: DefaultCoreRulesetVolumePath,
ReadOnly: true,
},
}...,
)
}
Expand Down Expand Up @@ -470,6 +480,17 @@ func (c *component) volumes() []corev1.Volume {
},
},
})

volumes = append(volumes, corev1.Volume{
Name: DefaultCoreRulesetVolumeName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: DefaultCoreRuleset,
},
},
},
})
}
}

Expand Down Expand Up @@ -515,6 +536,19 @@ func (c *component) modSecurityConfigMap() *corev1.ConfigMap {
}
}

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

//go:embed envoy-config.yaml.template
var envoyConfigTemplate string

Expand Down
59 changes: 51 additions & 8 deletions pkg/render/applicationlayer/applicationlayer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package applicationlayer_test
import (
"path/filepath"

coreruleset "github.com/corazawaf/coraza-coreruleset/v4"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

Expand Down Expand Up @@ -225,6 +226,14 @@ var _ = Describe("Tigera Secure Application Layer rendering tests", func() {
cm, err := embed.AsConfigMap(
applicationlayer.ModSecurityRulesetConfigMapName,
common.OperatorNamespace(),
embed.FS,
)
Expect(err).To(BeNil())

defaultCoreRulesetCM, err := embed.AsConfigMap(
applicationlayer.DefaultCoreRuleset,
common.OperatorNamespace(),
coreruleset.FS,
)
Expect(err).To(BeNil())

Expand Down Expand Up @@ -266,6 +275,7 @@ var _ = Describe("Tigera Secure Application Layer rendering tests", func() {

cfg.PerHostWAFEnabled = true
cfg.ModSecurityConfigMap = cm
cfg.DefaultCoreRulesetConfigMap = defaultCoreRulesetCM

component := applicationlayer.ApplicationLayer(cfg)

Expand Down Expand Up @@ -517,21 +527,30 @@ var _ = Describe("Tigera Secure Application Layer rendering tests", func() {
}{
{name: applicationlayer.APLName, ns: common.CalicoNamespace, group: "", version: "v1", kind: "ServiceAccount"},
{name: applicationlayer.ModSecurityRulesetConfigMapName, ns: common.CalicoNamespace, group: "", version: "v1", kind: "ConfigMap"},
{name: applicationlayer.DefaultCoreRuleset, ns: common.CalicoNamespace, group: "", version: "v1", kind: "ConfigMap"},
{name: applicationlayer.EnvoyConfigMapName, ns: common.CalicoNamespace, group: "", version: "v1", kind: "ConfigMap"},
{name: applicationlayer.ApplicationLayerDaemonsetName, ns: common.CalicoNamespace, group: "apps", version: "v1", kind: "DaemonSet"},
}
// Should render the correct resources.
cm, err := embed.AsConfigMap(
applicationlayer.ModSecurityRulesetConfigMapName,
common.OperatorNamespace(),
embed.FS,
)
Expect(err).To(BeNil())
defaultCoreRulesetCM, err := embed.AsConfigMap(
applicationlayer.DefaultCoreRuleset,
common.OperatorNamespace(),
coreruleset.FS,
)
Expect(err).To(BeNil())
component := applicationlayer.ApplicationLayer(&applicationlayer.Config{
PullSecrets: nil,
Installation: installation,
OsType: rmeta.OSTypeLinux,
PerHostWAFEnabled: true,
ModSecurityConfigMap: cm,
PullSecrets: nil,
Installation: installation,
OsType: rmeta.OSTypeLinux,
PerHostWAFEnabled: true,
ModSecurityConfigMap: cm,
DefaultCoreRulesetConfigMap: defaultCoreRulesetCM,
})
resources, _ := component.Objects()
Expect(len(resources)).To(Equal(len(expectedResources)))
Expand All @@ -552,7 +571,7 @@ var _ = Describe("Tigera Secure Application Layer rendering tests", func() {
// Ensure each volume rendered correctly.
dsVols := ds.Spec.Template.Spec.Volumes
hp := corev1.HostPathDirectoryOrCreate
expectedVolumes := []corev1.Volume{
correctVolumesOrder := []corev1.Volume{
{
Name: applicationlayer.FelixSync,
VolumeSource: corev1.VolumeSource{
Expand Down Expand Up @@ -601,13 +620,36 @@ var _ = Describe("Tigera Secure Application Layer rendering tests", func() {
},
},
},
{
Name: applicationlayer.DefaultCoreRuleset,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{Name: applicationlayer.DefaultCoreRuleset},
},
},
},
}

Expect(len(ds.Spec.Template.Spec.Volumes)).To(Equal(len(expectedVolumes)))
for i, expected := range expectedVolumes {
var modsecVolIndex, defaultCoreRulesecVolIndex int

Expect(len(ds.Spec.Template.Spec.Volumes)).To(Equal(len(correctVolumesOrder)))
for i, expected := range correctVolumesOrder {
if dsVols[i].Name == applicationlayer.ModSecurityRulesetConfigMapName {
modsecVolIndex = i
}

if dsVols[i].Name == applicationlayer.DefaultCoreRuleset {
defaultCoreRulesecVolIndex = i
}
Expect(dsVols[i]).To(Equal(expected))
}

// order of the volume mounts matter here
// coreruleset-default is mounted as a sub directory in the
// modsecurity volume so, modsecurity needs to be mounted before
// coreruleset-default
Expect(modsecVolIndex).Should(BeNumerically("<", defaultCoreRulesecVolIndex))

// Ensure that tolerations rendered correctly.
dsTolerations := ds.Spec.Template.Spec.Tolerations
expectedToleration := rmeta.TolerateAll
Expand Down Expand Up @@ -670,6 +712,7 @@ var _ = Describe("Tigera Secure Application Layer rendering tests", func() {
{Name: applicationlayer.FelixSync, MountPath: "/var/run/felix"},
{Name: applicationlayer.CalicoLogsVolumeName, MountPath: applicationlayer.CalicologsVolumePath},
{Name: applicationlayer.ModSecurityRulesetConfigMapName, MountPath: applicationlayer.ModSecurityRulesetVolumePath, ReadOnly: true},
{Name: applicationlayer.DefaultCoreRuleset, MountPath: applicationlayer.DefaultCoreRulesetVolumePath, ReadOnly: true},
}
Expect(len(dikastesVolMounts)).To(Equal(len(expectedDikastesVolMounts)))
for _, expected := range expectedDikastesVolMounts {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CoreRuleset activation
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
Include /etc/modsecurity-ruleset/coreruleset-default/*.conf

SecRuleEngine DetectionOnly

Expand Down
10 changes: 5 additions & 5 deletions pkg/render/applicationlayer/embed/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func init() {
}
}

func AsMap() (map[string]string, error) {
func AsMap(fileSystem fs.FS) (map[string]string, error) {
res := make(map[string]string)
var walkFn fs.WalkDirFunc = func(path string, d fs.DirEntry, err error) error {
if err != nil {
Expand All @@ -48,23 +48,23 @@ func AsMap() (map[string]string, error) {
return err
}

if b, err := fs.ReadFile(FS, path); err != nil {
if b, err := fs.ReadFile(fileSystem, path); err != nil {
return err
} else {
res[d.Name()] = string(b)
}
return nil
}

if err := fs.WalkDir(FS, ".", walkFn); err != nil {
if err := fs.WalkDir(fileSystem, ".", walkFn); err != nil {
return nil, fmt.Errorf("failed to walk core ruleset files (%w)", err)
}

return res, nil
}

func AsConfigMap(name, namespace string) (*corev1.ConfigMap, error) {
data, err := AsMap()
func AsConfigMap(name, namespace string, fileSystem fs.FS) (*corev1.ConfigMap, error) {
data, err := AsMap(fileSystem)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading