Merge pull request #71396 from liggitt/forbidden-messages

Improve node authorizer and noderestriction forbidden messages
This commit is contained in:
k8s-ci-robot 2018-11-30 00:04:46 -08:00 committed by GitHub
commit 5289fab2f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 28 deletions

View File

@ -124,7 +124,7 @@ func (c *nodePlugin) Admit(a admission.Attributes) error {
case "eviction": case "eviction":
return c.admitPodEviction(nodeName, a) return c.admitPodEviction(nodeName, a)
default: default:
return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q", a.GetSubresource())) return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q, only 'status' and 'eviction' are allowed", a.GetSubresource()))
} }
case nodeResource: case nodeResource:
@ -218,7 +218,7 @@ func (c *nodePlugin) admitPod(nodeName string, a admission.Attributes) error {
return nil return nil
default: default:
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation())) return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q, node %q can only create and delete mirror pods", a.GetOperation(), nodeName))
} }
} }
@ -280,7 +280,7 @@ func (c *nodePlugin) admitPVCStatus(nodeName string, a admission.Attributes) err
switch a.GetOperation() { switch a.GetOperation() {
case admission.Update: case admission.Update:
if !c.features.Enabled(features.ExpandPersistentVolumes) { if !c.features.Enabled(features.ExpandPersistentVolumes) {
return admission.NewForbidden(a, fmt.Errorf("node %q may not update persistentvolumeclaim metadata", nodeName)) return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update persistentvolumeclaim metadata", nodeName))
} }
oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim) oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
@ -310,7 +310,7 @@ func (c *nodePlugin) admitPVCStatus(nodeName string, a admission.Attributes) err
// ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc // ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc
if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) { if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) {
return admission.NewForbidden(a, fmt.Errorf("node %q may not update fields other than status.capacity and status.conditions: %v", nodeName, diff.ObjectReflectDiff(oldPVC, newPVC))) return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update fields other than status.capacity and status.conditions: %v", nodeName, diff.ObjectReflectDiff(oldPVC, newPVC)))
} }
return nil return nil
@ -331,14 +331,14 @@ func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error {
// Don't allow a node to create its Node API object with the config source set. // Don't allow a node to create its Node API object with the config source set.
// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation. // We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
if node.Spec.ConfigSource != nil { if node.Spec.ConfigSource != nil {
return admission.NewForbidden(a, fmt.Errorf("cannot create with non-nil configSource")) return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to create pods with a non-nil configSource", nodeName))
} }
// Don't allow a node to register with labels outside the allowed set. // Don't allow a node to register with labels outside the allowed set.
// This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself. // This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself.
modifiedLabels := getModifiedLabels(node.Labels, nil) modifiedLabels := getModifiedLabels(node.Labels, nil)
if forbiddenLabels := c.getForbiddenCreateLabels(modifiedLabels); len(forbiddenLabels) > 0 { if forbiddenLabels := c.getForbiddenCreateLabels(modifiedLabels); len(forbiddenLabels) > 0 {
return admission.NewForbidden(a, fmt.Errorf("cannot set labels: %s", strings.Join(forbiddenLabels.List(), ", "))) return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to set the following labels: %s", nodeName, strings.Join(forbiddenLabels.List(), ", ")))
} }
// check and warn if nodes set labels on create that would have been forbidden on update // check and warn if nodes set labels on create that would have been forbidden on update
// TODO(liggitt): in 1.17, expand getForbiddenCreateLabels to match getForbiddenUpdateLabels and drop this // TODO(liggitt): in 1.17, expand getForbiddenCreateLabels to match getForbiddenUpdateLabels and drop this
@ -352,7 +352,7 @@ func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error {
} }
} }
if requestedName != nodeName { if requestedName != nodeName {
return admission.NewForbidden(a, fmt.Errorf("node %q cannot modify node %q", nodeName, requestedName)) return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to modify node %q", nodeName, requestedName))
} }
if a.GetOperation() == admission.Update { if a.GetOperation() == admission.Update {
@ -369,20 +369,20 @@ func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error {
// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation. // We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
// We only do the check if the new node's configSource is non-nil; old kubelets might drop the field during a status update. // We only do the check if the new node's configSource is non-nil; old kubelets might drop the field during a status update.
if node.Spec.ConfigSource != nil && !apiequality.Semantic.DeepEqual(node.Spec.ConfigSource, oldNode.Spec.ConfigSource) { if node.Spec.ConfigSource != nil && !apiequality.Semantic.DeepEqual(node.Spec.ConfigSource, oldNode.Spec.ConfigSource) {
return admission.NewForbidden(a, fmt.Errorf("node %q cannot update configSource to a new non-nil configSource", nodeName)) return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update configSource to a new non-nil configSource", nodeName))
} }
// Don't allow a node to update its own taints. This would allow a node to remove or modify its // Don't allow a node to update its own taints. This would allow a node to remove or modify its
// taints in a way that would let it steer disallowed workloads to itself. // taints in a way that would let it steer disallowed workloads to itself.
if !apiequality.Semantic.DeepEqual(node.Spec.Taints, oldNode.Spec.Taints) { if !apiequality.Semantic.DeepEqual(node.Spec.Taints, oldNode.Spec.Taints) {
return admission.NewForbidden(a, fmt.Errorf("node %q cannot modify taints", nodeName)) return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to modify taints", nodeName))
} }
// Don't allow a node to update labels outside the allowed set. // Don't allow a node to update labels outside the allowed set.
// This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself. // This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself.
modifiedLabels := getModifiedLabels(node.Labels, oldNode.Labels) modifiedLabels := getModifiedLabels(node.Labels, oldNode.Labels)
if forbiddenUpdateLabels := c.getForbiddenUpdateLabels(modifiedLabels); len(forbiddenUpdateLabels) > 0 { if forbiddenUpdateLabels := c.getForbiddenUpdateLabels(modifiedLabels); len(forbiddenUpdateLabels) > 0 {
return admission.NewForbidden(a, fmt.Errorf("cannot modify labels: %s", strings.Join(forbiddenUpdateLabels.List(), ", "))) return admission.NewForbidden(a, fmt.Errorf("is not allowed to modify labels: %s", strings.Join(forbiddenUpdateLabels.List(), ", ")))
} }
} }

View File

@ -868,7 +868,7 @@ func Test_nodePlugin_Admit(t *testing.T) {
name: "forbid create of my node with forbidden labels", name: "forbid create of my node with forbidden labels",
podsGetter: noExistingPods, podsGetter: noExistingPods,
attributes: admission.NewAttributesRecord(setForbiddenCreateLabels(mynodeObj, ""), nil, nodeKind, mynodeObj.Namespace, "", nodeResource, "", admission.Create, false, mynode), attributes: admission.NewAttributesRecord(setForbiddenCreateLabels(mynodeObj, ""), nil, nodeKind, mynodeObj.Namespace, "", nodeResource, "", admission.Create, false, mynode),
err: `cannot set labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo`, err: `is not allowed to set the following labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo`,
}, },
{ {
name: "allow update of my node", name: "allow update of my node",
@ -892,7 +892,7 @@ func Test_nodePlugin_Admit(t *testing.T) {
name: "forbid create of my node with non-nil configSource", name: "forbid create of my node with non-nil configSource",
podsGetter: noExistingPods, podsGetter: noExistingPods,
attributes: admission.NewAttributesRecord(mynodeObjConfigA, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, false, mynode), attributes: admission.NewAttributesRecord(mynodeObjConfigA, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, false, mynode),
err: "create with non-nil configSource", err: "is not allowed to create pods with a non-nil configSource",
}, },
{ {
name: "forbid update of my node: nil configSource to new non-nil configSource", name: "forbid update of my node: nil configSource to new non-nil configSource",
@ -964,37 +964,37 @@ func Test_nodePlugin_Admit(t *testing.T) {
name: "forbid update of my node: add taints", name: "forbid update of my node: add taints",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode),
err: "cannot modify taints", err: "is not allowed to modify taints",
}, },
{ {
name: "forbid update of my node: remove taints", name: "forbid update of my node: remove taints",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObjTaintA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(mynodeObj, mynodeObjTaintA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode),
err: "cannot modify taints", err: "is not allowed to modify taints",
}, },
{ {
name: "forbid update of my node: change taints", name: "forbid update of my node: change taints",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObjTaintB, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObjTaintB, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode),
err: "cannot modify taints", err: "is not allowed to modify taints",
}, },
{ {
name: "forbid update of my node: add labels", name: "forbid update of my node: add labels",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(setForbiddenUpdateLabels(mynodeObj, ""), mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(setForbiddenUpdateLabels(mynodeObj, ""), mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode),
err: `cannot modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`, err: `is not allowed to modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`,
}, },
{ {
name: "forbid update of my node: remove labels", name: "forbid update of my node: remove labels",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(mynodeObj, setForbiddenUpdateLabels(mynodeObj, ""), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(mynodeObj, setForbiddenUpdateLabels(mynodeObj, ""), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode),
err: `cannot modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`, err: `is not allowed to modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`,
}, },
{ {
name: "forbid update of my node: change labels", name: "forbid update of my node: change labels",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(setForbiddenUpdateLabels(mynodeObj, "new"), setForbiddenUpdateLabels(mynodeObj, "old"), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(setForbiddenUpdateLabels(mynodeObj, "new"), setForbiddenUpdateLabels(mynodeObj, "old"), nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, false, mynode),
err: `cannot modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`, err: `is not allowed to modify labels: foo.node-restriction.kubernetes.io/foo, node-restriction.kubernetes.io/foo, other.k8s.io/foo, other.kubernetes.io/foo`,
}, },
// Other node object // Other node object
@ -1002,31 +1002,31 @@ func Test_nodePlugin_Admit(t *testing.T) {
name: "forbid create of other node", name: "forbid create of other node",
podsGetter: noExistingPods, podsGetter: noExistingPods,
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Create, false, mynode), attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Create, false, mynode),
err: "cannot modify node", err: "is not allowed to modify node",
}, },
{ {
name: "forbid create of other node pulling name from object", name: "forbid create of other node pulling name from object",
podsGetter: noExistingPods, podsGetter: noExistingPods,
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, "", nodeResource, "", admission.Create, false, mynode), attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, "", nodeResource, "", admission.Create, false, mynode),
err: "cannot modify node", err: "is not allowed to modify node",
}, },
{ {
name: "forbid update of other node", name: "forbid update of other node",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Update, false, mynode),
err: "cannot modify node", err: "is not allowed to modify node",
}, },
{ {
name: "forbid delete of other node", name: "forbid delete of other node",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(nil, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Delete, false, mynode), attributes: admission.NewAttributesRecord(nil, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Delete, false, mynode),
err: "cannot modify node", err: "is not allowed to modify node",
}, },
{ {
name: "forbid update of other node status", name: "forbid update of other node status",
podsGetter: existingPods, podsGetter: existingPods,
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "status", admission.Update, false, mynode), attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "status", admission.Update, false, mynode),
err: "cannot modify node", err: "is not allowed to modify node",
}, },
// Service accounts // Service accounts

View File

@ -196,11 +196,11 @@ func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, att
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
if err != nil { if err != nil {
klog.V(2).Infof("NODE DENY: %v", err) klog.V(2).Infof("NODE DENY: %v", err)
return authorizer.DecisionNoOpinion, "no path found to object", nil return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil
} }
if !ok { if !ok {
klog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs) klog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "no path found to object", nil return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil
} }
return authorizer.DecisionAllow, "", nil return authorizer.DecisionAllow, "", nil
} }
@ -221,11 +221,11 @@ func (r *NodeAuthorizer) authorizeCreateToken(nodeName string, startingType vert
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
if err != nil { if err != nil {
klog.V(2).Infof("NODE DENY: %v", err) klog.V(2).Infof("NODE DENY: %v", err)
return authorizer.DecisionNoOpinion, "no path found to object", nil return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil
} }
if !ok { if !ok {
klog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs) klog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "no path found to object", nil return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil
} }
return authorizer.DecisionAllow, "", nil return authorizer.DecisionAllow, "", nil
} }
@ -333,7 +333,7 @@ func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, s
return found return found
}) })
if !found { if !found {
return false, fmt.Errorf("node %q cannot get %s %s/%s, no path was found", nodeName, vertexTypes[startingType], startingNamespace, startingName) return false, fmt.Errorf("node %q cannot get %s %s/%s, no relationship to this object was found in the node authorizer graph", nodeName, vertexTypes[startingType], startingNamespace, startingName)
} }
return true, nil return true, nil
} }