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 waf default core ruleset as configmap #3643

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
69 changes: 55 additions & 14 deletions pkg/controller/applicationlayer/applicationlayer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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 +126,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 +145,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 +265,13 @@ func (r *ReconcileApplicationLayer) Reconcile(ctx context.Context, request recon
}

var passthroughModSecurityRuleSet bool
var modSecurityRuleSet *corev1.ConfigMap
var modSecurityRuleSet, corazaRuleset *corev1.ConfigMap
if r.isWAFEnabled(&instance.Spec) || r.isSidecarInjectionEnabled(&instance.Spec) {
if corazaRuleset, err = r.getCorazaRuleSet(ctx); err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error getting Web Application Firewall 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 +284,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: corazaRuleset,
UseRemoteAddressXFF: instance.Spec.EnvoySettings.UseRemoteAddress,
NumTrustedHopsXFF: instance.Spec.EnvoySettings.XFFNumTrustedHops,
ApplicationLayer: instance,
}
component := applicationlayer.ApplicationLayer(config)

Expand Down Expand Up @@ -450,6 +467,30 @@ 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 (r *ReconcileApplicationLayer) getCorazaRuleSet(ctx context.Context) (*corev1.ConfigMap, error) {
ruleset := new(corev1.ConfigMap)

ruleset, err := getOSWAPCoreRuleSet(ctx)
if err != nil {
return nil, err
}
return ruleset, nil
}

func getOSWAPCoreRuleSet(ctx context.Context) (*corev1.ConfigMap, error) {
ruleset, err := embed.AsConfigMap(
applicationlayer.DefaultCoreRuleset,
common.OperatorNamespace(),
coreruleset.FS,
electricjesus marked this conversation as resolved.
Show resolved Hide resolved
)
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/coreruleset-default"
LorcanMcVeigh marked this conversation as resolved.
Show resolved Hide resolved
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,
},
{
LorcanMcVeigh marked this conversation as resolved.
Show resolved Hide resolved
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
38 changes: 33 additions & 5 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 Down Expand Up @@ -601,6 +620,14 @@ 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)))
Expand Down Expand Up @@ -670,6 +697,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
14 changes: 9 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) {
electricjesus marked this conversation as resolved.
Show resolved Hide resolved
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,27 @@ func AsMap() (map[string]string, error) {
return err
}

if b, err := fs.ReadFile(FS, path); err != nil {
if d.Name()[0] == '@' {
Copy link
Member

Choose a reason for hiding this comment

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

not sure why this is done? are we skipping any directories prefixed with '@'? I thought rules were in a directory named '@owasp-' something

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes that works by accident, need to make the logic clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"by accident" i think you mean "very skilfully"

Copy link
Contributor

Choose a reason for hiding this comment

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

Changing this turned out to be easier than anticipated 😄
image

return err
}

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
2 changes: 1 addition & 1 deletion pkg/render/applicationlayer/embed/embed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestEmbed(t *testing.T) {
}

func TestEmbedAsMap(t *testing.T) {
fileMap, err := AsMap()
fileMap, err := AsMap(FS)
require.NoError(t, err)
for _, fileName := range []string{
"tigera.conf",
Expand Down
Loading