-
Notifications
You must be signed in to change notification settings - Fork 4.8k
respect scopes in list/watch projects #10252
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ import ( | |
| "k8s.io/kubernetes/pkg/util/sets" | ||
|
|
||
| authorizationapi "github.com/openshift/origin/pkg/authorization/api" | ||
| "github.com/openshift/origin/pkg/authorization/authorizer" | ||
| "github.com/openshift/origin/pkg/client" | ||
| oauthapi "github.com/openshift/origin/pkg/oauth/api" | ||
| projectapi "github.com/openshift/origin/pkg/project/api" | ||
|
|
@@ -48,6 +49,41 @@ func ScopesToRules(scopes []string, namespace string, clusterPolicyGetter client | |
| return rules, kutilerrors.NewAggregate(errors) | ||
| } | ||
|
|
||
| // ScopesToVisibleNamespaces returns a list of namespaces that the provided scopes have "get" access to. | ||
| // This exists only to support efficiently list/watch of projects (ACLed namespaces) | ||
| func ScopesToVisibleNamespaces(scopes []string, clusterPolicyGetter client.ClusterPolicyLister) (sets.String, error) { | ||
| if len(scopes) == 0 { | ||
| return sets.NewString("*"), nil | ||
| } | ||
|
|
||
| visibleNamespaces := sets.String{} | ||
|
|
||
| errors := []error{} | ||
| for _, scope := range scopes { | ||
| found := false | ||
|
|
||
| for _, evaluator := range ScopeEvaluators { | ||
| if evaluator.Handles(scope) { | ||
| found = true | ||
| allowedNamespaces, err := evaluator.ResolveGettableNamespaces(scope, clusterPolicyGetter) | ||
| if err != nil { | ||
| errors = append(errors, err) | ||
| continue | ||
| } | ||
|
|
||
| visibleNamespaces.Insert(allowedNamespaces...) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we now contain
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That assumes this knows how the results will be used. I'd rather not, but if you feel strongly I won't fight. |
||
| break | ||
| } | ||
| } | ||
|
|
||
| if !found { | ||
| errors = append(errors, fmt.Errorf("no scope evaluator found for %q", scope)) | ||
| } | ||
| } | ||
|
|
||
| return visibleNamespaces, kutilerrors.NewAggregate(errors) | ||
| } | ||
|
|
||
| const ( | ||
| UserIndicator = "user:" | ||
| ClusterRoleIndicator = "role:" | ||
|
|
@@ -61,6 +97,7 @@ type ScopeEvaluator interface { | |
| Describe(scope string) string | ||
| Validate(scope string) error | ||
| ResolveRules(scope, namespace string, clusterPolicyGetter client.ClusterPolicyLister) ([]authorizationapi.PolicyRule, error) | ||
| ResolveGettableNamespaces(scope string, clusterPolicyGetter client.ClusterPolicyLister) ([]string, error) | ||
| } | ||
|
|
||
| // ScopeEvaluators map prefixes to a function that handles that prefix | ||
|
|
@@ -134,7 +171,7 @@ func (userEvaluator) ResolveRules(scope, namespace string, clusterPolicyGetter c | |
| }, nil | ||
| case UserListProject: | ||
| return []authorizationapi.PolicyRule{ | ||
| {Verbs: sets.NewString("list"), APIGroups: []string{projectapi.GroupName}, Resources: sets.NewString("projects")}, | ||
| {Verbs: sets.NewString("list", "watch"), APIGroups: []string{projectapi.GroupName}, Resources: sets.NewString("projects")}, | ||
| }, nil | ||
| case UserFull: | ||
| return []authorizationapi.PolicyRule{ | ||
|
|
@@ -146,6 +183,15 @@ func (userEvaluator) ResolveRules(scope, namespace string, clusterPolicyGetter c | |
| } | ||
| } | ||
|
|
||
| func (userEvaluator) ResolveGettableNamespaces(scope string, clusterPolicyGetter client.ClusterPolicyLister) ([]string, error) { | ||
| switch scope { | ||
| case UserFull: | ||
| return []string{"*"}, nil | ||
| default: | ||
| return []string{}, nil | ||
| } | ||
| } | ||
|
|
||
| // escalatingScopeResources are resources that are considered escalating for scope evaluation | ||
| var escalatingScopeResources = []unversioned.GroupResource{ | ||
| {Group: kapi.GroupName, Resource: "secrets"}, | ||
|
|
@@ -175,6 +221,12 @@ func (e clusterRoleEvaluator) parseScope(scope string) (string /*role name*/, st | |
| if !e.Handles(scope) { | ||
| return "", "", false, fmt.Errorf("bad format for scope %v", scope) | ||
| } | ||
| return ParseClusterRoleScope(scope) | ||
| } | ||
| func ParseClusterRoleScope(scope string) (string /*role name*/, string /*namespace*/, bool /*escalating*/, error) { | ||
| if !strings.HasPrefix(scope, ClusterRoleIndicator) { | ||
| return "", "", false, fmt.Errorf("bad format for scope %v", scope) | ||
| } | ||
| escalating := false | ||
| if strings.HasSuffix(scope, ":!") { | ||
| escalating = true | ||
|
|
@@ -214,7 +266,7 @@ func (e clusterRoleEvaluator) Describe(scope string) string { | |
| } | ||
|
|
||
| func (e clusterRoleEvaluator) ResolveRules(scope, namespace string, clusterPolicyGetter client.ClusterPolicyLister) ([]authorizationapi.PolicyRule, error) { | ||
| roleName, scopeNamespace, escalating, err := e.parseScope(scope) | ||
| _, scopeNamespace, _, err := e.parseScope(scope) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
@@ -224,6 +276,16 @@ func (e clusterRoleEvaluator) ResolveRules(scope, namespace string, clusterPolic | |
| return []authorizationapi.PolicyRule{}, nil | ||
| } | ||
|
|
||
| return e.resolveRules(scope, clusterPolicyGetter) | ||
| } | ||
|
|
||
| // resolveRules doesn't enforce namespace checks | ||
| func (e clusterRoleEvaluator) resolveRules(scope string, clusterPolicyGetter client.ClusterPolicyLister) ([]authorizationapi.PolicyRule, error) { | ||
| roleName, _, escalating, err := e.parseScope(scope) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| policy, err := clusterPolicyGetter.Get("default") | ||
| if err != nil { | ||
| return nil, err | ||
|
|
@@ -252,6 +314,37 @@ func (e clusterRoleEvaluator) ResolveRules(scope, namespace string, clusterPolic | |
| return rules, nil | ||
| } | ||
|
|
||
| func (e clusterRoleEvaluator) ResolveGettableNamespaces(scope string, clusterPolicyGetter client.ClusterPolicyLister) ([]string, error) { | ||
| _, scopeNamespace, _, err := e.parseScope(scope) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| rules, err := e.resolveRules(scope, clusterPolicyGetter) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| attributes := authorizer.DefaultAuthorizationAttributes{ | ||
| APIGroup: kapi.GroupName, | ||
| Verb: "get", | ||
| Resource: "namespaces", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. normally, there's a ResourceName in the attributes we pass to RuleMatches()... trying to reason about what it means when there isn't one
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If a rule has no resource names, it can match. If the rule has a name, someone would have had to create a rule with Most rules won't have names on namespace rules. Those that do will be honored. Those that don't behave properly.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
| } | ||
|
|
||
| errors := []error{} | ||
| for _, rule := range rules { | ||
| matches, err := attributes.RuleMatches(rule) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we also need to check that one of the following is true?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It won't because the rule is only applied in a given namespace. Admin has "get" on "namespace" and the rule is only checked if the role is bound in that namespace.
I think the "I said he could look at an invalid namespace name" is a small enough edge to accept. That role never worked right.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, just checking |
||
| if err != nil { | ||
| errors = append(errors, err) | ||
| continue | ||
| } | ||
| if matches { | ||
| return []string{scopeNamespace}, nil | ||
| } | ||
| } | ||
|
|
||
| return []string{}, kutilerrors.NewAggregate(errors) | ||
| } | ||
|
|
||
| // TODO: direct deep copy needing a cloner is something that should be fixed upstream | ||
| var localCloner = conversion.NewCloner() | ||
|
|
||
|
|
||
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.
break ranging over evaluators or let them all have a shot at it?
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.
sure.