package authorizer import ( "fmt" "path" "strings" "k8s.io/kubernetes/pkg/util/sets" authorizationapi "github.com/openshift/origin/pkg/authorization/api" ) type DefaultAuthorizationAttributes struct { Verb string APIVersion string APIGroup string Resource string ResourceName string RequestAttributes interface{} NonResourceURL bool URL string } // ToDefaultAuthorizationAttributes coerces Action to DefaultAuthorizationAttributes. Namespace is not included // because the authorizer takes that information on the context func ToDefaultAuthorizationAttributes(in authorizationapi.Action) DefaultAuthorizationAttributes { return DefaultAuthorizationAttributes{ Verb: in.Verb, APIGroup: in.Group, APIVersion: in.Version, Resource: in.Resource, ResourceName: in.ResourceName, } } func (a DefaultAuthorizationAttributes) RuleMatches(rule authorizationapi.PolicyRule) (bool, error) { if a.IsNonResourceURL() { if a.nonResourceMatches(rule) { if a.verbMatches(rule.Verbs) { return true, nil } } return false, nil } if a.verbMatches(rule.Verbs) { if a.apiGroupMatches(rule.APIGroups) { allowedResourceTypes := authorizationapi.NormalizeResources(rule.Resources) if a.resourceMatches(allowedResourceTypes) { if a.nameMatches(rule.ResourceNames) { // this rule matches the request, so we should check the additional restrictions to be sure that it's allowed if rule.AttributeRestrictions != nil { switch rule.AttributeRestrictions.(type) { case (*authorizationapi.IsPersonalSubjectAccessReview): return IsPersonalAccessReview(a) default: return false, fmt.Errorf("unable to interpret: %#v", rule.AttributeRestrictions) } } return true, nil } } } } return false, nil } func (a DefaultAuthorizationAttributes) apiGroupMatches(allowedGroups []string) bool { // allowedGroups is expected to be small, so I don't feel bad about this. for _, allowedGroup := range allowedGroups { if allowedGroup == authorizationapi.APIGroupAll { return true } if strings.ToLower(allowedGroup) == strings.ToLower(a.GetAPIGroup()) { return true } } return false } func (a DefaultAuthorizationAttributes) verbMatches(verbs sets.String) bool { return verbs.Has(authorizationapi.VerbAll) || verbs.Has(strings.ToLower(a.GetVerb())) } func (a DefaultAuthorizationAttributes) resourceMatches(allowedResourceTypes sets.String) bool { return allowedResourceTypes.Has(authorizationapi.ResourceAll) || allowedResourceTypes.Has(strings.ToLower(a.GetResource())) } // nameMatches checks to see if the resourceName of the action is in a the specified whitelist. An empty whitelist indicates that any name is allowed. // An empty string in the whitelist should only match the action's resourceName if the resourceName itself is empty string. This behavior allows for the // combination of a whitelist for gets in the same rule as a list that won't have a resourceName. I don't recommend writing such a rule, but we do // handle it like you'd expect: white list is respected for gets while not preventing the list you explicitly asked for. func (a DefaultAuthorizationAttributes) nameMatches(allowedResourceNames sets.String) bool { if len(allowedResourceNames) == 0 { return true } return allowedResourceNames.Has(a.GetResourceName()) } func (a DefaultAuthorizationAttributes) GetVerb() string { return a.Verb } // nonResourceMatches take the remainer of a URL and attempts to match it against a series of explicitly allowed steps that can end in a wildcard func (a DefaultAuthorizationAttributes) nonResourceMatches(rule authorizationapi.PolicyRule) bool { for allowedNonResourcePath := range rule.NonResourceURLs { // if the allowed resource path ends in a wildcard, check to see if the URL starts with it if strings.HasSuffix(allowedNonResourcePath, "*") { if strings.HasPrefix(a.GetURL(), allowedNonResourcePath[0:len(allowedNonResourcePath)-1]) { return true } } // if we have an exact match, return true if a.GetURL() == allowedNonResourcePath { return true } } return false } // splitPath returns the segments for a URL path. func splitPath(thePath string) []string { thePath = strings.Trim(path.Clean(thePath), "/") if thePath == "" { return []string{} } return strings.Split(thePath, "/") } // DefaultAuthorizationAttributes satisfies the Action interface var _ Action = DefaultAuthorizationAttributes{} func (a DefaultAuthorizationAttributes) GetAPIVersion() string { return a.APIVersion } func (a DefaultAuthorizationAttributes) GetAPIGroup() string { return a.APIGroup } func (a DefaultAuthorizationAttributes) GetResource() string { return a.Resource } func (a DefaultAuthorizationAttributes) GetResourceName() string { return a.ResourceName } func (a DefaultAuthorizationAttributes) GetRequestAttributes() interface{} { return a.RequestAttributes } func (a DefaultAuthorizationAttributes) IsNonResourceURL() bool { return a.NonResourceURL } func (a DefaultAuthorizationAttributes) GetURL() string { return a.URL }