Skip to content
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

Merged
merged 3 commits into from
Feb 27, 2018

Conversation

mikedanese
Copy link
Member

@mikedanese mikedanese commented Nov 2, 2017

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

The node authorizer now allows nodes to request service account tokens for the service accounts of pods running on them.

@k8s-ci-robot k8s-ci-robot added sig/auth Categorizes an issue or PR as relevant to SIG Auth. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Nov 2, 2017
@liggitt
Copy link
Member

liggitt commented Nov 2, 2017

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))
Copy link
Member

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

@tallclair tallclair assigned tallclair and unassigned timstclair Nov 6, 2017
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
Copy link
Member

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"

Copy link
Member

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.

Copy link
Member Author

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.

@mikedanese
Copy link
Member Author

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.

@liggitt
Copy link
Member

liggitt commented Nov 10, 2017

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)

@mikedanese
Copy link
Member Author

updated this to work with the new design.

@k8s-github-robot k8s-github-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 19, 2018
@k8s-github-robot k8s-github-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 19, 2018
@kubernetes kubernetes deleted a comment from k8s-github-robot Jan 19, 2018
Copy link
Member

@tallclair tallclair left a 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/nade/node

Copy link
Member Author

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,
},
Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@ericchiang ericchiang changed the title auth: allow nodes to impersonate svcaccts of pods auth: allow nodes to create tokens for svcaccts of pods Jan 25, 2018
@liggitt
Copy link
Member

liggitt commented Jan 26, 2018

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:

  • update rbac system:nodes role to grant create on serviceaccounts/token for people using just RBAC
  • the NodeRestriction admission plugin to require token requests from nodes to be bound to a pod (and ensure that pod's nodeName matches the pod and serviceAccountName matches the service account)
  • integration test exercising authorizer+admission

@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Feb 23, 2018
@@ -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 {
Copy link
Member

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.

Copy link
Member

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?

Copy link
Member Author

@mikedanese mikedanese Feb 24, 2018

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.

Copy link
Member

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.

Copy link
Member

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
Copy link
Member

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?

Copy link
Member Author

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/running/scheduled/

Copy link
Member Author

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)
Copy link
Member

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?

Copy link
Member Author

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) {
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

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
Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@mikedanese
Copy link
Member Author

/retest

@liggitt
Copy link
Member

liggitt commented Feb 26, 2018

/lgtm

@k8s-ci-robot k8s-ci-robot added lgtm "Looks good to me", indicates that a PR is ready to be merged. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Feb 26, 2018
@mikedanese
Copy link
Member Author

mikedanese commented Feb 26, 2018

I need to add some TODOs
/hold

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Feb 26, 2018
@mikedanese mikedanese mentioned this pull request Feb 26, 2018
2 tasks
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
@k8s-ci-robot
Copy link
Contributor

[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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-github-robot k8s-github-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Feb 26, 2018
@mikedanese
Copy link
Member Author

/retest

@mikedanese mikedanese added lgtm "Looks good to me", indicates that a PR is ready to be merged. and removed do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. labels Feb 26, 2018
@k8s-github-robot
Copy link

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.

@k8s-github-robot k8s-github-robot merged commit 513e67a into kubernetes:master Feb 27, 2018
@mikedanese mikedanese deleted the svcacct branch February 27, 2018 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/auth Categorizes an issue or PR as relevant to SIG Auth. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants