Skip to content

Commit b343ec5

Browse files
Merge pull request #17185 from smarterclayton/scc_cant_be_patched
Automatic merge from submit-queue (batch tested with PRs 17160, 17185). SCC can't be patched via JSONPatch because users is nil When users or groups are nil, standard JSONPatch can't be used to add a new item to the list because the array is nil instead of empty. Alter the serialization of SCC so that there is always a user or group array returned. ``` oc patch "securitycontextconstraints.v1.security.openshift.io" "hostnetwork" --type=json --patch="[{\"op\":\"add\",\"path\":\"/users/-\",\"value\":\"system:serviceaccount:myproject:router\"}]" Error from server: jsonpatch add operation does not apply: doc is missing path: /users/- ``` This allows us to do declarative patching against SCC until we move to PSP in a future release. @liggitt realized this while trying to switch router to a declarative model - patch is our best option for update, but you can't actually do a safe addition without JSONPatch and without this change. /kind bug
2 parents 3a62c36 + 8f9995b commit b343ec5

File tree

8 files changed

+102
-5
lines changed

8 files changed

+102
-5
lines changed

api/protobuf-spec/github_com_openshift_origin_pkg_security_apis_security_v1.proto

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/swagger-spec/api-v1.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22774,7 +22774,9 @@
2277422774
"allowHostPorts",
2277522775
"allowHostPID",
2277622776
"allowHostIPC",
22777-
"readOnlyRootFilesystem"
22777+
"readOnlyRootFilesystem",
22778+
"users",
22779+
"groups"
2277822780
],
2277922781
"properties": {
2278022782
"kind": {

pkg/security/apis/security/v1/defaults.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ func SetDefaults_SCC(scc *SecurityContextConstraints) {
2323
scc.SupplementalGroups.Type = SupplementalGroupsStrategyRunAsAny
2424
}
2525

26+
if scc.Users == nil {
27+
scc.Users = []string{}
28+
}
29+
if scc.Groups == nil {
30+
scc.Groups = []string{}
31+
}
32+
2633
var defaultAllowedVolumes sets.String
2734
switch {
2835
case scc.Volumes == nil:

pkg/security/apis/security/v1/generated.proto

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/security/apis/security/v1/types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ type SecurityContextConstraints struct {
7979
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem" protobuf:"varint,17,opt,name=readOnlyRootFilesystem"`
8080

8181
// The users who have permissions to use this security context constraints
82-
Users []string `json:"users,omitempty" protobuf:"bytes,18,rep,name=users"`
82+
// +optional
83+
Users []string `json:"users" protobuf:"bytes,18,rep,name=users"`
8384
// The groups that have permission to use this security context constraints
84-
Groups []string `json:"groups,omitempty" protobuf:"bytes,19,rep,name=groups"`
85+
// +optional
86+
Groups []string `json:"groups" protobuf:"bytes,19,rep,name=groups"`
8587

8688
// SeccompProfiles lists the allowed profiles that may be set for the pod or
8789
// container's seccomp annotations. An unset (nil) or empty value means that no profiles may

pkg/security/apis/security/v1/zz_generated.conversion.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,8 +518,16 @@ func autoConvert_security_SecurityContextConstraints_To_v1_SecurityContextConstr
518518
}
519519
out.ReadOnlyRootFilesystem = in.ReadOnlyRootFilesystem
520520
out.SeccompProfiles = *(*[]string)(unsafe.Pointer(&in.SeccompProfiles))
521-
out.Users = *(*[]string)(unsafe.Pointer(&in.Users))
522-
out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups))
521+
if in.Users == nil {
522+
out.Users = make([]string, 0)
523+
} else {
524+
out.Users = *(*[]string)(unsafe.Pointer(&in.Users))
525+
}
526+
if in.Groups == nil {
527+
out.Groups = make([]string, 0)
528+
} else {
529+
out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups))
530+
}
523531
return nil
524532
}
525533

pkg/security/registry/securitycontextconstraints/strategy.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,27 @@ func (strategy) PrepareForCreate(_ genericapirequest.Context, obj runtime.Object
5050
func (strategy) PrepareForUpdate(_ genericapirequest.Context, obj, old runtime.Object) {
5151
}
5252

53+
// Canonicalize removes duplicate user and group values, preserving order.
5354
func (strategy) Canonicalize(obj runtime.Object) {
55+
scc := obj.(*securityapi.SecurityContextConstraints)
56+
scc.Users = uniqueStrings(scc.Users)
57+
scc.Groups = uniqueStrings(scc.Groups)
58+
}
59+
60+
func uniqueStrings(values []string) []string {
61+
if len(values) < 2 {
62+
return values
63+
}
64+
updated := make([]string, 0, len(values))
65+
existing := make(map[string]struct{})
66+
for _, value := range values {
67+
if _, ok := existing[value]; ok {
68+
continue
69+
}
70+
existing[value] = struct{}{}
71+
updated = append(updated, value)
72+
}
73+
return updated
5474
}
5575

5676
func (strategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package securitycontextconstraints
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
securityapi "github.com/openshift/origin/pkg/security/apis/security"
8+
)
9+
10+
func TestCanonicalize(t *testing.T) {
11+
testCases := []struct {
12+
obj *securityapi.SecurityContextConstraints
13+
expect *securityapi.SecurityContextConstraints
14+
}{
15+
{
16+
obj: &securityapi.SecurityContextConstraints{},
17+
expect: &securityapi.SecurityContextConstraints{},
18+
},
19+
{
20+
obj: &securityapi.SecurityContextConstraints{
21+
Users: []string{"a"},
22+
},
23+
expect: &securityapi.SecurityContextConstraints{
24+
Users: []string{"a"},
25+
},
26+
},
27+
{
28+
obj: &securityapi.SecurityContextConstraints{
29+
Users: []string{"a", "a"},
30+
Groups: []string{"b", "b"},
31+
},
32+
expect: &securityapi.SecurityContextConstraints{
33+
Users: []string{"a"},
34+
Groups: []string{"b"},
35+
},
36+
},
37+
{
38+
obj: &securityapi.SecurityContextConstraints{
39+
Users: []string{"a", "b", "a"},
40+
Groups: []string{"c", "d", "c"},
41+
},
42+
expect: &securityapi.SecurityContextConstraints{
43+
Users: []string{"a", "b"},
44+
Groups: []string{"c", "d"},
45+
},
46+
},
47+
}
48+
for i, testCase := range testCases {
49+
Strategy.Canonicalize(testCase.obj)
50+
if !reflect.DeepEqual(testCase.expect, testCase.obj) {
51+
t.Errorf("%d: unexpected object: %#v", i, testCase.obj)
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)