Skip to content

Commit 3df3056

Browse files
authored
Merge pull request #694 from esticansat/fix-604-seccomp-rule-update
Fix for issue #604 - Seccomp rules check updates
2 parents cf555d2 + db670ee commit 3df3056

20 files changed

Lines changed: 650 additions & 454 deletions

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ $ kubesec ./score-9-deployment.yml | jq --exit-status '.score > 10' >/dev/null
295295
296296
## Example output
297297
298-
Kubesec returns a returns a JSON array, and can scan multiple YAML documents in a single input file.
298+
Kubesec returns a JSON array, and can scan multiple YAML documents in a single input file.
299299
300300
```json
301301
[
@@ -327,15 +327,21 @@ Kubesec returns a returns a JSON array, and can scan multiple YAML documents in
327327
]
328328
```
329329
330+
> [!NOTE]
331+
> You can also cat multiple files, as long as they're correctly formatted as multiple documents separated by `---`. E.g.
332+
> ```
333+
> { cat test/asset/multi.yml;
334+
> echo "---";
335+
> cat test/asset/critical-double-multiple.yml;
336+
> } | dist/kubesec scan -
337+
> ```
338+
330339
---
331340
332-
## Contributors
333341
334-
Thanks to our awesome contributors!
342+
## Contributing
335343
336-
- [Andrew Martin](@sublimino)
337-
- [Stefan Prodan](@stefanprodan)
338-
- [Jack Kelly](@06kellyjac)
344+
Check out [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
339345
340346
## Getting Help
341347
@@ -348,7 +354,7 @@ If you have any questions about Kubesec and Kubernetes security:
348354
Your feedback is always welcome!
349355
350356
[testing_workflow]: https://github.com/controlplaneio/kubesec/actions?query=workflow%3ATesting
351-
[testing_workflow_badge]: https://github.com/controlplaneio/kubesec/workflows/Testing/badge.svg
357+
[testing_workflow_badge]: https://github.com/controlplaneio/kubesec/actions/workflows/test_acceptance.yml/badge.svg
352358
[security_workflow]: https://github.com/controlplaneio/kubesec/actions?query=workflow%3A%22Security+Analysis%22
353359
[security_workflow_badge]: https://github.com/controlplaneio/kubesec/workflows/Security%20Analysis/badge.svg
354360
[release_workflow]: https://github.com/controlplaneio/kubesec/actions?query=workflow%3ARelease

pkg/ruler/ruleset.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ func NewRuleset(logger *zap.SugaredLogger) *Ruleset {
230230
seccompAnyRule := Rule{
231231
Predicate: rules.SeccompAny,
232232
ID: "SeccompAny",
233-
Selector: ".metadata .annotations .\"container.seccomp.security.alpha.kubernetes.io/pod\"",
233+
Selector: ".spec .securityContext .seccompProfile .type | .spec .containers[] .securityContext .seccompProfile .type | .spec .initContainers[] .securityContext .seccompProfile .type | .spec .ephemeralContainers[] .securityContext .seccompProfile .type",
234234
Reason: "Seccomp profiles set minimum privilege and secure against unknown threats",
235235
Kinds: []string{"Pod", "Deployment", "StatefulSet", "DaemonSet"},
236236
Points: 1,
@@ -240,7 +240,7 @@ func NewRuleset(logger *zap.SugaredLogger) *Ruleset {
240240
seccompUnconfinedRule := Rule{
241241
Predicate: rules.SeccompUnconfined,
242242
ID: "SeccompUnconfined",
243-
Selector: ".metadata .annotations .\"container.seccomp.security.alpha.kubernetes.io/pod\"",
243+
Selector: ".spec .securityContext .seccompProfile .type | .spec .containers[] .securityContext .seccompProfile .type | .spec .initContainers[] .securityContext .seccompProfile .type | .spec .ephemeralContainers[] .securityContext .seccompProfile .type",
244244
Reason: "Unconfined Seccomp profiles have full system call access",
245245
Kinds: []string{"Pod", "Deployment", "StatefulSet", "DaemonSet"},
246246
Points: -1,

pkg/rules/helper.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func checkSecurityContext(json []byte, checkPodSecurityContext bool, checkFn che
4141

4242
containersLen := jq.Copy().From(spec + ".containers").Count()
4343
initContainersLen := jq.Copy().From(spec + ".initContainers").Count()
44+
ephemeralContainersLen := jq.Copy().From(spec + ".ephemeralContainers").Count()
4445

4546
var valid int
4647

@@ -60,6 +61,7 @@ func checkSecurityContext(json []byte, checkPodSecurityContext bool, checkFn che
6061

6162
ctnFn(initContainersLen, "initContainers")
6263
ctnFn(containersLen, "containers")
64+
ctnFn(ephemeralContainersLen, "ephemeralContainers")
6365

6466
return valid
6567
}

pkg/rules/seccompAny.go

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

33
import (
4-
"bytes"
5-
"fmt"
64
"github.com/thedevsaddam/gojsonq/v2"
7-
"regexp"
8-
"strings"
95
)
106

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

23-
if capDrop != nil && strings.Contains(capDropString, "seccomp.security.alpha.kubernetes.io/pod:") {
24-
if !strings.Contains(capDropString, "seccomp.security.alpha.kubernetes.io/pod:unconfined") {
25-
containers++
26-
}
27-
} else if capDrop != nil {
13+
v, ok := value.(string)
2814

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

33-
if isNamedPodMatch {
34-
isUnconfinedNamedPodMatch, _ := regexp.MatchString(startWordBoundaryRegex+keyNameRegex+":unconfined"+endWordBoundaryRegex, capDropString)
35-
if !isUnconfinedNamedPodMatch {
36-
containers++
37-
}
38-
}
21+
return checkSecurityContextResult{
22+
valid: (v == "Unconfined") == expectedUnconfined,
3923
}
24+
}
4025

41-
return containers
26+
// SeccompAny retrieves the number of instances in a manifest where the Seccomp profile has been specified
27+
// to a value other than 'Unconfined'
28+
func SeccompAny(json []byte) int {
29+
return checkSecurityContext(
30+
json,
31+
true, // present in PodSecurityContext
32+
func(jq *gojsonq.JSONQ) checkSecurityContextResult {
33+
return isSeccompUnconfined(jq, false)
34+
})
4235
}

0 commit comments

Comments
 (0)