Skip to content

Commit 18dfae9

Browse files
Merge pull request #19772 from enj/enj/i/pod_node_constraints_mirror_pod/1579605
Ignore node created self targeting mirror pods in PodNodeConstraints
2 parents 7950131 + 5d958b0 commit 18dfae9

File tree

2 files changed

+83
-11
lines changed

2 files changed

+83
-11
lines changed

pkg/scheduler/admission/podnodeconstraints/admission.go

+43-7
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,43 @@ import (
1515
"k8s.io/apiserver/pkg/authorization/authorizer"
1616
kapi "k8s.io/kubernetes/pkg/apis/core"
1717
"k8s.io/kubernetes/pkg/apis/extensions"
18+
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
1819

1920
"github.com/openshift/origin/pkg/api/meta"
2021
configlatest "github.com/openshift/origin/pkg/cmd/server/apis/config/latest"
2122
"github.com/openshift/origin/pkg/scheduler/admission/apis/podnodeconstraints"
2223
)
2324

25+
const PluginName = "PodNodeConstraints"
26+
2427
// kindsToIgnore is a list of kinds that contain a PodSpec that
2528
// we choose not to handle in this plugin
2629
var kindsToIgnore = []schema.GroupKind{
2730
extensions.Kind("DaemonSet"),
2831
}
2932

3033
func Register(plugins *admission.Plugins) {
31-
plugins.Register("PodNodeConstraints",
34+
plugins.Register(PluginName,
3235
func(config io.Reader) (admission.Interface, error) {
3336
pluginConfig, err := readConfig(config)
3437
if err != nil {
3538
return nil, err
3639
}
3740
if pluginConfig == nil {
38-
glog.Infof("Admission plugin %q is not configured so it will be disabled.", "PodNodeConstraints")
41+
glog.Infof("Admission plugin %q is not configured so it will be disabled.", PluginName)
3942
return nil, nil
4043
}
41-
return NewPodNodeConstraints(pluginConfig), nil
44+
return NewPodNodeConstraints(pluginConfig, nodeidentifier.NewDefaultNodeIdentifier()), nil
4245
})
4346
}
4447

4548
// NewPodNodeConstraints creates a new admission plugin to prevent objects that contain pod templates
4649
// from containing node bindings by name or selector based on role permissions.
47-
func NewPodNodeConstraints(config *podnodeconstraints.PodNodeConstraintsConfig) admission.Interface {
50+
func NewPodNodeConstraints(config *podnodeconstraints.PodNodeConstraintsConfig, nodeIdentifier nodeidentifier.NodeIdentifier) admission.Interface {
4851
plugin := podNodeConstraints{
49-
config: config,
50-
Handler: admission.NewHandler(admission.Create, admission.Update),
52+
config: config,
53+
Handler: admission.NewHandler(admission.Create, admission.Update),
54+
nodeIdentifier: nodeIdentifier,
5155
}
5256
if config != nil {
5357
plugin.selectorLabelBlacklist = sets.NewString(config.NodeSelectorLabelBlacklist...)
@@ -61,6 +65,7 @@ type podNodeConstraints struct {
6165
selectorLabelBlacklist sets.String
6266
config *podnodeconstraints.PodNodeConstraintsConfig
6367
authorizer authorizer.Authorizer
68+
nodeIdentifier nodeidentifier.NodeIdentifier
6469
}
6570

6671
func shouldCheckResource(resource schema.GroupResource, kind schema.GroupKind) (bool, error) {
@@ -135,6 +140,12 @@ func (o *podNodeConstraints) getPodSpec(attr admission.Attributes) (kapi.PodSpec
135140

136141
// validate PodSpec if NodeName or NodeSelector are specified
137142
func (o *podNodeConstraints) admitPodSpec(attr admission.Attributes, ps kapi.PodSpec) error {
143+
// a node creating a mirror pod that targets itself is allowed
144+
// see the NodeRestriction plugin for further details
145+
if o.isNodeSelfTargetWithMirrorPod(attr, ps.NodeName) {
146+
return nil
147+
}
148+
138149
matchingLabels := []string{}
139150
// nodeSelector blacklist filter
140151
for nodeSelectorLabel := range ps.NodeSelector {
@@ -168,7 +179,10 @@ func (o *podNodeConstraints) SetAuthorizer(a authorizer.Authorizer) {
168179

169180
func (o *podNodeConstraints) ValidateInitialization() error {
170181
if o.authorizer == nil {
171-
return fmt.Errorf("PodNodeConstraints needs an Openshift Authorizer")
182+
return fmt.Errorf("%s requires an authorizer", PluginName)
183+
}
184+
if o.nodeIdentifier == nil {
185+
return fmt.Errorf("%s requires a node identifier", PluginName)
172186
}
173187
return nil
174188
}
@@ -190,3 +204,25 @@ func (o *podNodeConstraints) checkPodsBindAccess(attr admission.Attributes) (boo
190204
authorized, _, err := o.authorizer.Authorize(authzAttr)
191205
return authorized == authorizer.DecisionAllow, err
192206
}
207+
208+
func (o *podNodeConstraints) isNodeSelfTargetWithMirrorPod(attr admission.Attributes, nodeName string) bool {
209+
// make sure we are actually trying to target a node
210+
if len(nodeName) == 0 {
211+
return false
212+
}
213+
// this check specifically requires the object to be pod (unlike the other checks where we want any pod spec)
214+
pod, ok := attr.GetObject().(*kapi.Pod)
215+
if !ok {
216+
return false
217+
}
218+
// note that anyone can create a mirror pod, but they are not privileged in any way
219+
// they are actually highly constrained since they cannot reference secrets
220+
// nodes can only create and delete them, and they will delete any "orphaned" mirror pods
221+
if _, isMirrorPod := pod.Annotations[kapi.MirrorPodAnnotationKey]; !isMirrorPod {
222+
return false
223+
}
224+
// we are targeting a node with a mirror pod
225+
// confirm the user is a node that is targeting itself
226+
actualNodeName, isNode := o.nodeIdentifier.NodeIdentity(attr.GetUserInfo())
227+
return isNode && actualNodeName == nodeName
228+
}

pkg/scheduler/admission/podnodeconstraints/admission_test.go

+40-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"k8s.io/kubernetes/pkg/apis/batch"
1616
kapi "k8s.io/kubernetes/pkg/apis/core"
1717
"k8s.io/kubernetes/pkg/apis/extensions"
18+
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
1819
"k8s.io/kubernetes/pkg/serviceaccount"
1920

2021
_ "github.com/openshift/origin/pkg/api/install"
@@ -99,11 +100,39 @@ func TestPodNodeConstraints(t *testing.T) {
99100
expectedResource: "pods/binding",
100101
expectedErrorMsg: "",
101102
},
103+
// 7: expect nodeName to succeed with node user self targeting mirror pod
104+
{
105+
config: testConfig(),
106+
resource: nodeNameMirrorPod(),
107+
userinfo: &user.DefaultInfo{Name: "system:node:frank", Groups: []string{user.NodesGroup}},
108+
expectedErrorMsg: "",
109+
},
110+
// 8: expect nodeName to fail with node user self targeting non-mirror pod
111+
{
112+
config: testConfig(),
113+
resource: nodeNamePod(),
114+
userinfo: &user.DefaultInfo{Name: "system:node:frank", Groups: []string{user.NodesGroup}},
115+
expectedErrorMsg: "node selection by nodeName is prohibited by policy for your role",
116+
},
117+
// 9: expect nodeName to fail with node user non-self targeting mirror pod
118+
{
119+
config: testConfig(),
120+
resource: nodeNameMirrorPod(),
121+
userinfo: &user.DefaultInfo{Name: "system:node:bob", Groups: []string{user.NodesGroup}},
122+
expectedErrorMsg: "node selection by nodeName is prohibited by policy for your role",
123+
},
124+
// 10: expect nodeName to fail with node user non-self targeting non-mirror pod
125+
{
126+
config: testConfig(),
127+
resource: nodeNamePod(),
128+
userinfo: &user.DefaultInfo{Name: "system:node:bob", Groups: []string{user.NodesGroup}},
129+
expectedErrorMsg: "node selection by nodeName is prohibited by policy for your role",
130+
},
102131
}
103132
for i, tc := range tests {
104133
var expectedError error
105134
errPrefix := fmt.Sprintf("%d", i)
106-
prc := NewPodNodeConstraints(tc.config)
135+
prc := NewPodNodeConstraints(tc.config, nodeidentifier.NewDefaultNodeIdentifier())
107136
prc.(initializer.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
108137
err := prc.(admission.InitializationValidator).ValidateInitialization()
109138
if err != nil {
@@ -123,7 +152,7 @@ func TestPodNodeConstraintsPodUpdate(t *testing.T) {
123152
ns := metav1.NamespaceDefault
124153
var expectedError error
125154
errPrefix := "PodUpdate"
126-
prc := NewPodNodeConstraints(testConfig())
155+
prc := NewPodNodeConstraints(testConfig(), nodeidentifier.NewDefaultNodeIdentifier())
127156
prc.(initializer.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
128157
err := prc.(admission.InitializationValidator).ValidateInitialization()
129158
if err != nil {
@@ -139,7 +168,7 @@ func TestPodNodeConstraintsNonHandledResources(t *testing.T) {
139168
ns := metav1.NamespaceDefault
140169
errPrefix := "ResourceQuotaTest"
141170
var expectedError error
142-
prc := NewPodNodeConstraints(testConfig())
171+
prc := NewPodNodeConstraints(testConfig(), nodeidentifier.NewDefaultNodeIdentifier())
143172
prc.(initializer.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
144173
err := prc.(admission.InitializationValidator).ValidateInitialization()
145174
if err != nil {
@@ -257,7 +286,7 @@ func TestPodNodeConstraintsResources(t *testing.T) {
257286
for _, top := range testops {
258287
var expectedError error
259288
errPrefix := fmt.Sprintf("%s; %s; %s", tr.prefix, tp.prefix, top.operation)
260-
prc := NewPodNodeConstraints(tc.config)
289+
prc := NewPodNodeConstraints(tc.config, nodeidentifier.NewDefaultNodeIdentifier())
261290
prc.(initializer.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t))
262291
err := prc.(admission.InitializationValidator).ValidateInitialization()
263292
if err != nil {
@@ -312,6 +341,13 @@ func nodeNamePod() *kapi.Pod {
312341
return pod
313342
}
314343

344+
func nodeNameMirrorPod() *kapi.Pod {
345+
pod := &kapi.Pod{}
346+
pod.Annotations = map[string]string{kapi.MirrorPodAnnotationKey: "true"}
347+
pod.Spec.NodeName = "frank"
348+
return pod
349+
}
350+
315351
func nodeSelectorPod() *kapi.Pod {
316352
pod := &kapi.Pod{}
317353
pod.Spec.NodeSelector = map[string]string{"bogus": "frank"}

0 commit comments

Comments
 (0)