-
Notifications
You must be signed in to change notification settings - Fork 40k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
auth: allow nodes to create tokens for svcaccts of pods #55019
Conversation
Outlining the use cases for this would be helpful. |
@@ -211,6 +214,8 @@ func (g *Graph) AddPod(pod *api.Pod) { | |||
nodeVertex := g.getOrCreateVertex_locked(nodeVertexType, "", pod.Spec.NodeName) | |||
g.graph.SetEdge(newDestinationEdge(podVertex, nodeVertex, nodeVertex)) | |||
|
|||
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(serviceAccountVertexType, pod.Namespace, pod.Spec.ServiceAccountName), podVertex, nodeVertex)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if len(pod.Spec.ServiceAccountName) > 0
func (r *NodeAuthorizer) authorizeImpersonate(nodeName string, startingType vertexType, attrs authorizer.Attributes) (bool, string, error) { | ||
if attrs.GetVerb() != "impersonate" || len(attrs.GetName()) == 0 { | ||
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) | ||
return false, "can only impersonate individual resources of this type", nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: error message isn't accurate if attrs.GetVerb() != "impersonate"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like the other (preexisting) methods make the same mistake, if you feel like cleaning up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "individual" makes the error message accurate.
Use case: I have a node agent that runs and uses the kubelet credentials. I want it to be able to make requests on behalf of pods running on the node to e.g. ask for service account JWTs. Allowing the node credentials to get serviceaccount tokens indirectly permits impersonation although we don't want to promote that behavior. This makes the permission explicit. |
I don't think we want impersonation for this... we'd only want a node to be able to request JWTs for a service account that included the node name (and maybe even the pod name/namespace/uid for which the token was requested) |
626d89a
to
7f4e09a
Compare
updated this to work with the new design. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, but I defer to @liggitt and @ericchiang
@@ -165,6 +168,31 @@ func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, att | |||
return authorizer.DecisionAllow, "", nil | |||
} | |||
|
|||
// authorizeCreateToken authorizes "create" requests to serviceaccounts 'token' | |||
// subresource of pods running on a nade |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/nade/node
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
name: "deny svcacct token create", | ||
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "serviceaccounts", Subresource: "token", Name: "svcacct0-node1", Namespace: "ns0"}, | ||
expect: authorizer.DecisionNoOpinion, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a case for non-create & non-token subresource
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
this looks like what I'd expect for this piece. will leave it open until the token request stuff gets in. related pieces needed as the tokenrequest bits are built out:
|
@@ -213,6 +216,10 @@ func (g *Graph) AddPod(pod *api.Pod) { | |||
nodeVertex := g.getOrCreateVertex_locked(nodeVertexType, "", pod.Spec.NodeName) | |||
g.graph.SetEdge(newDestinationEdge(podVertex, nodeVertex, nodeVertex)) | |||
|
|||
if len(pod.Spec.ServiceAccountName) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the pod doesn't mount the service account secrets, should the node still get access to the service account? If it does, then this is actually opening up the node permissions a little bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To frame this differently, should the token request be authorized by access to the service account, or the service account secret?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good point that I hadn't thought of. I'm inclined to say no since the motivation of this change was to decouple service account credentials from the secretvolumes. However this might be reasonable once we have a serviceaccountvolumesource but if this is actually desirable we might want to allow pods to run without serviceaccounts at all. Is there any point of a pod running as a service account if it can't get credentials for that service account? @liggitt what do you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any point of a pod running as a service account if it can't get credentials for that service account?
For one thing, a pod must always run with a service account (except static pods). By not mounting the credentials, it just gives a bit more defense (e.g. against authz misconfig). Another reason to run as a SA w/o creds is to use a PodSecurityPolicy, which are authorized for the pod's SA.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would still expect the node to have access... @smarterclayton and I have been stewing on how image pulling interacts with the pod identity... thinking through how you could have a node-level image pull credential plugin that made use of service account information.
I'd leave this as is for now, with a TODO to resolve before exiting alpha
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) | ||
if err != nil { | ||
glog.V(2).Infof("NODE DENY: %v", err) | ||
return authorizer.DecisionNoOpinion, "no path found to object", nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: shouldn't this be an error return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I don't think this is an error. This gets hit if a node requests a token for a service account when no pods running that service account are scheduled on that node. This should only return an error on server errors, not client errors.
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject())) | ||
} | ||
// TokenRequests from a node must have a pod binding. That pod must be | ||
// running on the node. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/running/scheduled/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
// Use the Node authorization to limit a node to create tokens for service accounts running on that node | ||
// Use the NodeRestriction admission plugin to limit a node to create tokens bound to pods on that node | ||
tokenRequestRule := rbac.NewRule("create").Groups(legacyGroup).Resources("serviceaccounts/token").RuleOrDie() | ||
nodePolicyRules = append(nodePolicyRules, tokenRequestRule) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just for legacy nodes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for people who haven't started using the node authorizer yet.
@@ -125,6 +130,12 @@ func (c *nodePlugin) Admit(a admission.Attributes) error { | |||
return admission.NewForbidden(a, fmt.Errorf("may only update PVC status")) | |||
} | |||
|
|||
case svcacctResource: | |||
if c.features.Enabled(features.TokenRequest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd actually deny altogether if the feature was disabled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not leave the current behavior if the feature is turned off? At this point we haven't even checked that the request was to the '/token' subresource. It's a bit weird to be doing denys in an admission controller on the resource attribute alone. Where there is enough information available to the authorizer to deny a request, we should prefer to deny the request there. All the information we would be making this deny decision on is available to the Node authorizer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not leave the current behavior if the feature is turned off?
Agreed. Feature gates should maintain the current behavior when disabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
if c.features.Enabled(features.TokenRequest) { | ||
return c.admitServiceAccount(nodeName, a) | ||
} | ||
fallthrough |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if matters if we deny above if the feature is disabled, but prefer return nil
over fallthrough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
/retest |
/lgtm |
I need to add some TODOs |
running on them.
nodes should only be able to create TokenRequests if: * token is bound to a pod * binding has uid and name * the pod exists * the pod is running on that node
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: liggitt, mikedanese The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
/retest |
Automatic merge from submit-queue (batch tested with PRs 59365, 60446, 60448, 55019, 60431). If you want to cherry-pick this change to another branch, please follow the instructions here. |
ref #58790
running on them. nodes essentially have the power to do this today
but not explicitly. this allows agents using the node identity to
take actions on behalf of local pods.
@kubernetes/sig-auth-pr-reviews @smarterclayton