package authorizer import ( "errors" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/auth/user" kerrors "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/sets" "github.com/openshift/origin/pkg/authorization/rulevalidation" ) type openshiftAuthorizer struct { ruleResolver rulevalidation.AuthorizationRuleResolver forbiddenMessageMaker ForbiddenMessageMaker } func NewAuthorizer(ruleResolver rulevalidation.AuthorizationRuleResolver, forbiddenMessageMaker ForbiddenMessageMaker) Authorizer { return &openshiftAuthorizer{ruleResolver, forbiddenMessageMaker} } func (a *openshiftAuthorizer) Authorize(ctx kapi.Context, passedAttributes Action) (bool, string, error) { attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes) user, ok := kapi.UserFrom(ctx) if !ok { return false, "", errors.New("no user available on context") } namespace, _ := kapi.NamespaceFrom(ctx) allowed, reason, err := a.authorizeWithNamespaceRules(user, namespace, attributes) if allowed { return true, reason, nil } // errors are allowed to occur if err != nil { return false, "", err } denyReason, err := a.forbiddenMessageMaker.MakeMessage(MessageContext{user, namespace, attributes}) if err != nil { denyReason = err.Error() } return false, denyReason, nil } // GetAllowedSubjects returns the subjects it knows can perform the action. // If we got an error, then the list of subjects may not be complete, but it does not contain any incorrect names. // This is done because policy rules are purely additive and policy determinations // can be made on the basis of those rules that are found. func (a *openshiftAuthorizer) GetAllowedSubjects(ctx kapi.Context, attributes Action) (sets.String, sets.String, error) { namespace, _ := kapi.NamespaceFrom(ctx) return a.getAllowedSubjectsFromNamespaceBindings(namespace, attributes) } func (a *openshiftAuthorizer) getAllowedSubjectsFromNamespaceBindings(namespace string, passedAttributes Action) (sets.String, sets.String, error) { attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes) var errs []error roleBindings, err := a.ruleResolver.GetRoleBindings(namespace) if err != nil { errs = append(errs, err) } users := sets.String{} groups := sets.String{} for _, roleBinding := range roleBindings { role, err := a.ruleResolver.GetRole(roleBinding) if err != nil { // If we got an error, then the list of subjects may not be complete, but it does not contain any incorrect names. // This is done because policy rules are purely additive and policy determinations // can be made on the basis of those rules that are found. errs = append(errs, err) continue } for _, rule := range role.Rules() { matches, err := attributes.RuleMatches(rule) if err != nil { errs = append(errs, err) continue } if matches { users.Insert(roleBinding.Users().List()...) groups.Insert(roleBinding.Groups().List()...) } } } return users, groups, kerrors.NewAggregate(errs) } // authorizeWithNamespaceRules returns isAllowed, reason, and error. If an error is returned, isAllowed and reason are still valid. This seems strange // but errors are not always fatal to the authorization process. It is entirely possible to get an error and be able to continue determine authorization // status in spite of it. This is most common when a bound role is missing, but enough roles are still present and bound to authorize the request. func (a *openshiftAuthorizer) authorizeWithNamespaceRules(user user.Info, namespace string, passedAttributes Action) (bool, string, error) { attributes := CoerceToDefaultAuthorizationAttributes(passedAttributes) allRules, ruleRetrievalError := a.ruleResolver.RulesFor(user, namespace) var errs []error for _, rule := range allRules { matches, err := attributes.RuleMatches(rule) if err != nil { errs = append(errs, err) continue } if matches { if len(namespace) == 0 { return true, "allowed by cluster rule", nil } // not 100% accurate, because the rule may have been provided by a cluster rule. we no longer have // this distinction upstream in practice. return true, "allowed by rule in " + namespace, nil } } if len(errs) == 0 { return false, "", ruleRetrievalError } if ruleRetrievalError != nil { errs = append(errs, ruleRetrievalError) } return false, "", kerrors.NewAggregate(errs) } // TODO this may or may not be the behavior we want for managing rules. As a for instance, a verb might be specified // that our attributes builder will never satisfy. For now, I think gets us close. Maybe a warning message of some kind? func CoerceToDefaultAuthorizationAttributes(passedAttributes Action) *DefaultAuthorizationAttributes { attributes, ok := passedAttributes.(*DefaultAuthorizationAttributes) if !ok { attributes = &DefaultAuthorizationAttributes{ APIGroup: passedAttributes.GetAPIGroup(), Verb: passedAttributes.GetVerb(), RequestAttributes: passedAttributes.GetRequestAttributes(), Resource: passedAttributes.GetResource(), ResourceName: passedAttributes.GetResourceName(), NonResourceURL: passedAttributes.IsNonResourceURL(), URL: passedAttributes.GetURL(), } } return attributes } func doesApplyToUser(ruleUsers, ruleGroups sets.String, user user.Info) bool { if ruleUsers.Has(user.GetName()) { return true } for _, currGroup := range user.GetGroups() { if ruleGroups.Has(currGroup) { return true } } return false }