From 27934da9ccb1fd8134823345365f3e6e58a15f0d Mon Sep 17 00:00:00 2001 From: mdshuai Date: Fri, 20 Nov 2015 10:42:02 +0800 Subject: [PATCH] Validate uids and gids for securitycontext --- pkg/api/validation/validation.go | 14 +++++ pkg/api/validation/validation_test.go | 78 ++++++++++++++++++++++++++ pkg/util/validation/validation.go | 20 +++++++ pkg/util/validation/validation_test.go | 32 +++++++++++ 4 files changed, 144 insertions(+) diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index a23fb4a8b70..8511d07c3cf 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -19,6 +19,7 @@ package validation import ( "encoding/json" "fmt" + "math" "net" "os" "path" @@ -58,6 +59,7 @@ var DNS1123LabelErrorMsg string = fmt.Sprintf(`must be a DNS label (at most %d c var DNS952LabelErrorMsg string = fmt.Sprintf(`must be a DNS 952 label (at most %d characters, matching regex %s): e.g. "my-name"`, validation.DNS952LabelMaxLength, validation.DNS952LabelFmt) var pdPartitionErrorMsg string = InclusiveRangeErrorMsg(1, 255) var PortRangeErrorMsg string = InclusiveRangeErrorMsg(1, 65535) +var IdRangeErrorMsg string = InclusiveRangeErrorMsg(0, math.MaxInt32) var PortNameErrorMsg string = fmt.Sprintf(`must be an IANA_SVC_NAME (at most 15 characters, matching regex %s, it must contain at least one letter [a-z], and hyphens cannot be adjacent to other hyphens): e.g. "http"`, validation.IdentifierNoHyphensBeginEndFmt) const totalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB @@ -1309,6 +1311,18 @@ func ValidatePodSecurityContext(securityContext *api.PodSecurityContext, spec *a if securityContext != nil { allErrs = append(allErrs, validateHostNetwork(securityContext.HostNetwork, spec.Containers, specPath.Child("containers"))...) + if securityContext.FSGroup != nil && !validation.IsValidGroupId(*securityContext.FSGroup) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("fsGroup"), *(securityContext.FSGroup), IdRangeErrorMsg)) + } + if securityContext.RunAsUser != nil && !validation.IsValidUserId(*securityContext.RunAsUser) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *(securityContext.RunAsUser), IdRangeErrorMsg)) + } + for i, gid := range securityContext.SupplementalGroups { + if !validation.IsValidGroupId(gid) { + supplementalGroup := fmt.Sprintf(`supplementalGroups[%d]`, i) + allErrs = append(allErrs, field.Invalid(fldPath.Child(supplementalGroup), gid, IdRangeErrorMsg)) + } + } } return allErrs diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index fef048c0e17..11897528a2d 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -1392,6 +1392,8 @@ func TestValidateDNSPolicy(t *testing.T) { func TestValidatePodSpec(t *testing.T) { activeDeadlineSeconds := int64(30) + minID := int64(0) + maxID := int64(2147483647) successCases := []api.PodSpec{ { // Populate basic fields, leave defaults for most. Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}}, @@ -1425,6 +1427,26 @@ func TestValidatePodSpec(t *testing.T) { RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, + { // Populate RunAsUser SupplementalGroups FSGroup with minID 0 + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + SupplementalGroups: []int64{minID}, + RunAsUser: &minID, + FSGroup: &minID, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + { // Populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647 + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + SupplementalGroups: []int64{maxID}, + RunAsUser: &maxID, + FSGroup: &maxID, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, { // Populate HostIPC. SecurityContext: &api.PodSecurityContext{ HostIPC: true, @@ -1451,6 +1473,8 @@ func TestValidatePodSpec(t *testing.T) { } activeDeadlineSeconds = int64(0) + minID = int64(-1) + maxID = int64(2147483648) failureCases := map[string]api.PodSpec{ "bad volume": { Volumes: []api.Volume{{}}, @@ -1495,6 +1519,60 @@ func TestValidatePodSpec(t *testing.T) { RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, + "bad supplementalGroups large than math.MaxInt32": { + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: false, + SupplementalGroups: []int64{maxID, 1234}, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + "bad supplementalGroups less than 0": { + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: false, + SupplementalGroups: []int64{minID, 1234}, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + "bad runAsUser large than math.MaxInt32": { + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: false, + RunAsUser: &maxID, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + "bad runAsUser less than 0": { + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: false, + RunAsUser: &minID, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + "bad fsGroup large than math.MaxInt32": { + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: false, + FSGroup: &maxID, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + "bad fsGroup less than 0": { + Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: false, + FSGroup: &minID, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, "bad-active-deadline-seconds": { Volumes: []api.Volume{ {Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}, diff --git a/pkg/util/validation/validation.go b/pkg/util/validation/validation.go index 854a7ee7b53..c41960f12fc 100644 --- a/pkg/util/validation/validation.go +++ b/pkg/util/validation/validation.go @@ -17,6 +17,7 @@ limitations under the License. package validation import ( + "math" "net" "regexp" "strings" @@ -105,6 +106,25 @@ func IsValidPortNum(port int) bool { return 0 < port && port < 65536 } +// Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1 +// TODO: once we have a type for UID/GID we should make these that type. +const ( + minUserID = 0 + maxUserID = math.MaxInt32 + minGroupID = 0 + maxGroupID = math.MaxInt32 +) + +// IsValidGroupId tests that the argument is a valid gids. +func IsValidGroupId(gid int64) bool { + return minGroupID <= gid && gid <= maxGroupID +} + +// IsValidUserId tests that the argument is a valid uids. +func IsValidUserId(uid int64) bool { + return minUserID <= uid && uid <= maxUserID +} + const doubleHyphensFmt string = ".*(--).*" var doubleHyphensRegexp = regexp.MustCompile("^" + doubleHyphensFmt + "$") diff --git a/pkg/util/validation/validation_test.go b/pkg/util/validation/validation_test.go index f8434493e09..80399c14626 100644 --- a/pkg/util/validation/validation_test.go +++ b/pkg/util/validation/validation_test.go @@ -154,6 +154,38 @@ func TestIsValidPortNum(t *testing.T) { } } +func TestIsValidGroupId(t *testing.T) { + goodValues := []int64{0, 1, 1000, 65535, 2147483647} + for _, val := range goodValues { + if !IsValidGroupId(val) { + t.Errorf("expected true for '%d'", val) + } + } + + badValues := []int64{-1, -1003, 2147483648, 4147483647} + for _, val := range badValues { + if IsValidGroupId(val) { + t.Errorf("expected false for '%d'", val) + } + } +} + +func TestIsValidUserId(t *testing.T) { + goodValues := []int64{0, 1, 1000, 65535, 2147483647} + for _, val := range goodValues { + if !IsValidUserId(val) { + t.Errorf("expected true for '%d'", val) + } + } + + badValues := []int64{-1, -1003, 2147483648, 4147483647} + for _, val := range badValues { + if IsValidUserId(val) { + t.Errorf("expected false for '%d'", val) + } + } +} + func TestIsValidPortName(t *testing.T) { goodValues := []string{"telnet", "re-mail-ck", "pop3", "a", "a-1", "1-a", "a-1-b-2-c", "1-a-2-b-3"} for _, val := range goodValues {