Skip to content

Commit e8d5e06

Browse files
authored
Merge pull request #701 from esticansat/fix-685-update-apparmor-detection-rules
Fix 685 update apparmor detection rules
2 parents 643ad50 + f533c72 commit e8d5e06

18 files changed

Lines changed: 665 additions & 267 deletions

pkg/ruler/ruleset.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,23 @@ func NewRuleset(logger *zap.SugaredLogger) *Ruleset {
250250
apparmorAnyRule := Rule{
251251
Predicate: rules.ApparmorAny,
252252
ID: "ApparmorAny",
253-
Selector: ".metadata .annotations .\"container.apparmor.security.beta.kubernetes.io/nginx\"",
254-
Reason: "Well defined AppArmor policies may provide greater protection from unknown threats. WARNING: NOT PRODUCTION READY",
253+
Selector: ".spec .securityContext .appArmorProfile .type | .spec .containers[] .securityContext .appArmorProfile .type | .spec .initContainers[] .securityContext .appArmorProfile .type | .spec .ephemeralContainers[] .securityContext .appArmorProfile .type",
254+
Reason: "Well defined AppArmor policies may provide greater protection from unknown threats.",
255255
Kinds: []string{"Pod", "Deployment", "StatefulSet", "DaemonSet"},
256256
Points: 3,
257257
}
258258
list = append(list, apparmorAnyRule)
259259

260+
apparmorUnconfinedRule := Rule{
261+
Predicate: rules.ApparmorUnconfined,
262+
ID: "ApparmorUnconfined",
263+
Selector: ".spec .securityContext .appArmorProfile .type | .spec .containers[] .securityContext .appArmorProfile .type | .spec .initContainers[] .securityContext .appArmorProfile .type | .spec .ephemeralContainers[] .securityContext .appArmorProfile .type",
264+
Reason: "Unconfined AppArmor profiles disable AppArmor enforcement on the workloads",
265+
Kinds: []string{"Pod", "Deployment", "StatefulSet", "DaemonSet"},
266+
Points: -1,
267+
}
268+
list = append(list, apparmorUnconfinedRule)
269+
260270
volumeClaimAccessModeReadWriteOnce := Rule{
261271
Predicate: rules.VolumeClaimAccessModeReadWriteOnce,
262272
ID: "VolumeClaimAccessModeReadWriteOnce",

pkg/rules/apparmorAny.go

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,33 @@
11
package rules
22

3-
import (
4-
"bytes"
5-
"fmt"
6-
"github.com/thedevsaddam/gojsonq/v2"
7-
"regexp"
8-
"strings"
9-
)
3+
import "github.com/thedevsaddam/gojsonq/v2"
104

11-
// TODO(ajm): tighten these matches, they could be "[apparmor..." or " apparmor...", and "unconfined]" or "unconfined "
12-
// TODO(ajm): space delimiting matches is insufficient as this could be set to `unconfined blah`
13-
func ApparmorAny(json []byte) int {
14-
containers := 0
15-
startWordBoundaryRegex := "[\\[ ]"
16-
endWordBoundaryRegex := "[\\] ]"
17-
18-
annotations := gojsonq.New().Reader(bytes.NewReader(json)).
19-
From("metadata.annotations").Get()
5+
// isApparmorUnconfined checks the appArmorProfile.type field and returns a checkSecurityContextResult struct.
6+
// If the field is set then unset=false. If, on top of that, the value of Unconfined matches the expected value
7+
// then return valid=true.
8+
func isApparmorUnconfined(jq *gojsonq.JSONQ, expectedUnconfined bool) checkSecurityContextResult {
9+
value := jq.From("securityContext.appArmorProfile.type").Get()
2010

21-
annotationsString := fmt.Sprintf("%v", annotations)
11+
v, ok := value.(string)
2212

23-
if annotations != nil && strings.Contains(annotationsString, "container.apparmor.security.beta.kubernetes.io/pod:") {
24-
if !strings.Contains(annotationsString, "container.apparmor.security.beta.kubernetes.io/pod:unconfined") {
25-
containers++
26-
}
27-
} else if annotations != nil {
13+
res := checkSecurityContextResult{}
2814

29-
keyNameRegex := "container\\.apparmor\\.security\\.beta\\.kubernetes\\.io/[a-zA-Z-.]+"
30-
// TODO(ajm) match end of string in regex
31-
isNamedPodMatch, _ := regexp.MatchString(startWordBoundaryRegex+keyNameRegex+":", annotationsString)
15+
if !ok {
16+
res.unset = true
17+
return res
18+
}
3219

33-
if isNamedPodMatch {
34-
isUnconfinedNamedPodMatch, _ := regexp.MatchString(startWordBoundaryRegex+keyNameRegex+":unconfined"+endWordBoundaryRegex, annotationsString)
35-
if !isUnconfinedNamedPodMatch {
36-
containers++
37-
}
38-
}
20+
return checkSecurityContextResult{
21+
valid: (v == "Unconfined") == expectedUnconfined,
3922
}
23+
}
4024

41-
return containers
25+
func ApparmorAny(json []byte) int {
26+
return checkSecurityContext(
27+
json,
28+
true, // present in Pod Security Context
29+
func(jq *gojsonq.JSONQ) checkSecurityContextResult {
30+
return isApparmorUnconfined(jq, false)
31+
},
32+
)
4233
}

0 commit comments

Comments
 (0)