diff --git a/pkg/security/securitycontextconstraints/byrestrictions.go b/pkg/security/securitycontextconstraints/byrestrictions.go index 91dba286be81..c616c139cddc 100644 --- a/pkg/security/securitycontextconstraints/byrestrictions.go +++ b/pkg/security/securitycontextconstraints/byrestrictions.go @@ -1,9 +1,12 @@ package securitycontextconstraints import ( + "strings" + "github.com/golang/glog" securityapi "github.com/openshift/origin/pkg/security/apis/security" + kapi "k8s.io/kubernetes/pkg/api" ) // ByRestrictions is a helper to sort SCCs in order of most restrictive to least restrictive. @@ -25,15 +28,24 @@ func (s ByRestrictions) Less(i, j int) bool { type points int const ( - privilegedPoints points = 20 + privilegedPoints points = 200000 + + hostVolumePoints points = 100000 + nonTrivialVolumePoints points = 50000 - hostVolumePoints points = 10 - nonTrivialVolumePoints points = 5 + runAsAnyUserPoints points = 40000 + runAsNonRootPoints points = 30000 + runAsRangePoints points = 20000 + runAsUserPoints points = 10000 - runAsAnyUserPoints points = 4 - runAsNonRootPoints points = 3 - runAsRangePoints points = 2 - runAsUserPoints points = 1 + capDefaultPoints points = 5000 + capAddOnePoints points = 300 + capAllowAllPoints points = 4000 + capAllowOnePoints points = 10 + capDropAllPoints points = -3000 + capDropOnePoints points = -50 + capMaxPoints points = 9999 + capMinPoints points = 0 noPoints points = 0 ) @@ -50,6 +62,9 @@ func pointValue(constraint *securityapi.SecurityContextConstraints) points { // add points based on volume requests totalPoints += volumePointValue(constraint) + // add points based on capabilities + totalPoints += capabilitiesPointValue(constraint) + // the map contains points for both RunAsUser and SELinuxContext // strategies by taking advantage that they have identical strategy names strategiesPoints := map[string]points{ @@ -79,9 +94,9 @@ func pointValue(constraint *securityapi.SecurityContextConstraints) points { } // volumePointValue returns a score based on the volumes allowed by the SCC. -// Allowing a host volume will return a score of 10. Allowance of anything other +// Allowing a host volume will return a score of 100000. Allowance of anything other // than Secret, ConfigMap, EmptyDir, DownwardAPI, Projected, and None will result in -// a score of 5. If the SCC only allows these trivial types, it will have a +// a score of 50000. If the SCC only allows these trivial types, it will have a // score of 0. func volumePointValue(scc *securityapi.SecurityContextConstraints) points { hasHostVolume := false @@ -111,3 +126,39 @@ func volumePointValue(scc *securityapi.SecurityContextConstraints) points { } return noPoints } + +// hasCap checks for needle in haystack. +func hasCap(needle string, haystack []kapi.Capability) bool { + for _, c := range haystack { + if needle == strings.ToUpper(string(c)) { + return true + } + } + return false +} + +// capabilitiesPointValue returns a score based on the capabilities allowed, +// added, or removed by the SCC. This allow us to prefer the more restrictive +// SCC. +func capabilitiesPointValue(scc *securityapi.SecurityContextConstraints) points { + capsPoints := capDefaultPoints + capsPoints += capAddOnePoints * points(len(scc.DefaultAddCapabilities)) + if hasCap(string(securityapi.AllowAllCapabilities), scc.AllowedCapabilities) { + capsPoints += capAllowAllPoints + } else if hasCap("ALL", scc.AllowedCapabilities) { + capsPoints += capAllowAllPoints + } else { + capsPoints += capAllowOnePoints * points(len(scc.AllowedCapabilities)) + } + if hasCap("ALL", scc.RequiredDropCapabilities) { + capsPoints += capDropAllPoints + } else { + capsPoints += capDropOnePoints * points(len(scc.RequiredDropCapabilities)) + } + if capsPoints > capMaxPoints { + return capMaxPoints + } else if capsPoints < capMinPoints { + return capMinPoints + } + return capsPoints +} diff --git a/pkg/security/securitycontextconstraints/byrestrictions_test.go b/pkg/security/securitycontextconstraints/byrestrictions_test.go index 673fdf9741a2..d561242f2aed 100644 --- a/pkg/security/securitycontextconstraints/byrestrictions_test.go +++ b/pkg/security/securitycontextconstraints/byrestrictions_test.go @@ -4,6 +4,7 @@ import ( "testing" securityapi "github.com/openshift/origin/pkg/security/apis/security" + kapi "k8s.io/kubernetes/pkg/api" ) func TestPointValue(t *testing.T) { @@ -33,7 +34,7 @@ func TestPointValue(t *testing.T) { // run through all combos of user strategy + seLinux strategy + priv for userStrategy, userStrategyPoints := range userStrategies { for seLinuxStrategy, seLinuxStrategyPoints := range seLinuxStrategies { - expectedPoints := privilegedPoints + userStrategyPoints + seLinuxStrategyPoints + expectedPoints := privilegedPoints + userStrategyPoints + seLinuxStrategyPoints + capDefaultPoints scc := newSCC(true, seLinuxStrategy, userStrategy) actualPoints := pointValue(scc) @@ -41,7 +42,7 @@ func TestPointValue(t *testing.T) { t.Errorf("privileged, user: %v, seLinux %v expected %d score but got %d", userStrategy, seLinuxStrategy, expectedPoints, actualPoints) } - expectedPoints = userStrategyPoints + seLinuxStrategyPoints + expectedPoints = userStrategyPoints + seLinuxStrategyPoints + capDefaultPoints scc = newSCC(false, seLinuxStrategy, userStrategy) actualPoints = pointValue(scc) @@ -51,14 +52,15 @@ func TestPointValue(t *testing.T) { } } - // sanity check to ensure volume score is added (specific volumes scores are tested below + // sanity check to ensure volume and capabilities scores are added (specific volumes + // and capabilities scores are tested below) scc := newSCC(false, securityapi.SELinuxStrategyMustRunAs, securityapi.RunAsUserStrategyMustRunAs) scc.Volumes = []securityapi.FSType{securityapi.FSTypeHostPath} actualPoints := pointValue(scc) - // SELinux + User + host path volume - expectedPoints := runAsUserPoints + runAsUserPoints + hostVolumePoints + // SELinux + User + host path volume + default capabilities + expectedPoints := runAsUserPoints + runAsUserPoints + hostVolumePoints + capDefaultPoints if actualPoints != expectedPoints { - t.Errorf("volume score was not added to the scc point value correctly!") + t.Errorf("volume score was not added to the scc point value correctly, got %d!", actualPoints) } } @@ -168,3 +170,94 @@ func TestVolumePointValue(t *testing.T) { } } } + +func TestCapabilitiesPointValue(t *testing.T) { + newSCC := func(def []kapi.Capability, allow []kapi.Capability, drop []kapi.Capability) *securityapi.SecurityContextConstraints { + return &securityapi.SecurityContextConstraints{ + DefaultAddCapabilities: def, + AllowedCapabilities: allow, + RequiredDropCapabilities: drop, + } + } + + tests := map[string]struct { + defaultAdd []kapi.Capability + allowed []kapi.Capability + requiredDrop []kapi.Capability + expectedPoints points + }{ + "nothing specified": { + defaultAdd: nil, + allowed: nil, + requiredDrop: nil, + expectedPoints: capDefaultPoints, + }, + "default": { + defaultAdd: []kapi.Capability{"KILL", "MKNOD"}, + allowed: nil, + requiredDrop: nil, + expectedPoints: capDefaultPoints + 2*capAddOnePoints, + }, + "allow": { + defaultAdd: nil, + allowed: []kapi.Capability{"KILL", "MKNOD"}, + requiredDrop: nil, + expectedPoints: capDefaultPoints + 2*capAllowOnePoints, + }, + "allow star": { + defaultAdd: nil, + allowed: []kapi.Capability{"*"}, + requiredDrop: nil, + expectedPoints: capDefaultPoints + capAllowAllPoints, + }, + "allow all": { + defaultAdd: nil, + allowed: []kapi.Capability{"ALL"}, + requiredDrop: nil, + expectedPoints: capDefaultPoints + capAllowAllPoints, + }, + "allow all case": { + defaultAdd: nil, + allowed: []kapi.Capability{"All"}, + requiredDrop: nil, + expectedPoints: capDefaultPoints + capAllowAllPoints, + }, + "drop": { + defaultAdd: nil, + allowed: nil, + requiredDrop: []kapi.Capability{"KILL", "MKNOD"}, + expectedPoints: capDefaultPoints + 2*capDropOnePoints, + }, + "drop all": { + defaultAdd: nil, + allowed: nil, + requiredDrop: []kapi.Capability{"ALL"}, + expectedPoints: capDefaultPoints + capDropAllPoints, + }, + "drop all case": { + defaultAdd: nil, + allowed: nil, + requiredDrop: []kapi.Capability{"all"}, + expectedPoints: capDefaultPoints + capDropAllPoints, + }, + "drop star": { + defaultAdd: nil, + allowed: nil, + requiredDrop: []kapi.Capability{"*"}, + expectedPoints: capDefaultPoints + capDropOnePoints, + }, + "mixture": { + defaultAdd: []kapi.Capability{"SETUID", "SETGID"}, + allowed: []kapi.Capability{"*"}, + requiredDrop: []kapi.Capability{"SYS_CHROOT"}, + expectedPoints: capDefaultPoints + 2*capAddOnePoints + capAllowAllPoints + capDropOnePoints, + }, + } + for k, v := range tests { + scc := newSCC(v.defaultAdd, v.allowed, v.requiredDrop) + actualPoints := capabilitiesPointValue(scc) + if actualPoints != v.expectedPoints { + t.Errorf("%s expected %d capability score but got %d", k, v.expectedPoints, actualPoints) + } + } +}