mirror of https://github.com/k3s-io/k3s
add selfsubjectrulesreview api
parent
6a845c67f0
commit
f14c138438
|
@ -441,7 +441,7 @@ func BuildGenericConfig(s *options.ServerRunOptions) (*genericapiserver.Config,
|
|||
return nil, nil, nil, nil, nil, fmt.Errorf("invalid authentication config: %v", err)
|
||||
}
|
||||
|
||||
genericConfig.Authorizer, err = BuildAuthorizer(s, sharedInformers)
|
||||
genericConfig.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, sharedInformers)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("invalid authorization config: %v", err)
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ func BuildAuthenticator(s *options.ServerRunOptions, storageFactory serverstorag
|
|||
}
|
||||
|
||||
// BuildAuthorizer constructs the authorizer
|
||||
func BuildAuthorizer(s *options.ServerRunOptions, sharedInformers informers.SharedInformerFactory) (authorizer.Authorizer, error) {
|
||||
func BuildAuthorizer(s *options.ServerRunOptions, sharedInformers informers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, error) {
|
||||
authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers)
|
||||
return authorizationConfig.New()
|
||||
}
|
||||
|
|
|
@ -190,7 +190,7 @@ func NonBlockingRun(s *options.ServerRunOptions, stopCh <-chan struct{}) error {
|
|||
sharedInformers := informers.NewSharedInformerFactory(client, 10*time.Minute)
|
||||
|
||||
authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers)
|
||||
apiAuthorizer, err := authorizationConfig.New()
|
||||
apiAuthorizer, _, err := authorizationConfig.New()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid Authorization Config: %v", err)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r
|
|||
&announced.GroupMetaFactoryArgs{
|
||||
GroupName: authorization.GroupName,
|
||||
VersionPreferenceOrder: []string{v1.SchemeGroupVersion.Version, v1beta1.SchemeGroupVersion.Version},
|
||||
RootScopedKinds: sets.NewString("SubjectAccessReview", "SelfSubjectAccessReview"),
|
||||
RootScopedKinds: sets.NewString("SubjectAccessReview", "SelfSubjectAccessReview", "SelfSubjectRulesReview"),
|
||||
AddInternalObjectsToScheme: authorization.AddToScheme,
|
||||
},
|
||||
announced.VersionToSchemeFunc{
|
||||
|
|
|
@ -44,6 +44,7 @@ var (
|
|||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&SelfSubjectRulesReview{},
|
||||
&SelfSubjectAccessReview{},
|
||||
&SubjectAccessReview{},
|
||||
&LocalSubjectAccessReview{},
|
||||
|
|
|
@ -149,3 +149,74 @@ type SubjectAccessReviewStatus struct {
|
|||
// For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.
|
||||
EvaluationError string
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:noVerbs
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace.
|
||||
// The returned list of actions may be incomplete depending on the server's authorization mode,
|
||||
// and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions,
|
||||
// or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to
|
||||
// drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns.
|
||||
// SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.
|
||||
type SelfSubjectRulesReview struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
|
||||
// Spec holds information about the request being evaluated.
|
||||
Spec SelfSubjectRulesReviewSpec
|
||||
|
||||
// Status is filled in by the server and indicates the set of actions a user can perform.
|
||||
Status SubjectRulesReviewStatus
|
||||
}
|
||||
|
||||
type SelfSubjectRulesReviewSpec struct {
|
||||
// Namespace to evaluate rules for. Required.
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on
|
||||
// the set of authorizers the server is configured with and any errors experienced during evaluation.
|
||||
// Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission,
|
||||
// even if that list is incomplete.
|
||||
type SubjectRulesReviewStatus struct {
|
||||
// ResourceRules is the list of actions the subject is allowed to perform on resources.
|
||||
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
|
||||
ResourceRules []ResourceRule
|
||||
// NonResourceRules is the list of actions the subject is allowed to perform on non-resources.
|
||||
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
|
||||
NonResourceRules []NonResourceRule
|
||||
// Incomplete is true when the rules returned by this call are incomplete. This is most commonly
|
||||
// encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.
|
||||
Incomplete bool
|
||||
// EvaluationError can appear in combination with Rules. It indicates an error occurred during
|
||||
// rule evaluation, such as an authorizer that doesn't support rule evaluation, and that
|
||||
// ResourceRules and/or NonResourceRules may be incomplete.
|
||||
EvaluationError string
|
||||
}
|
||||
|
||||
// ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant,
|
||||
// may contain duplicates, and possibly be incomplete.
|
||||
type ResourceRule struct {
|
||||
// Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all.
|
||||
Verbs []string
|
||||
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
|
||||
// the enumerated resources in any API group will be allowed. "*" means all.
|
||||
APIGroups []string
|
||||
// Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.
|
||||
Resources []string
|
||||
// ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all.
|
||||
ResourceNames []string
|
||||
}
|
||||
|
||||
// NonResourceRule holds information that describes a rule for the non-resource
|
||||
type NonResourceRule struct {
|
||||
// Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all.
|
||||
Verbs []string
|
||||
|
||||
// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full,
|
||||
// final step in the path. "*" means all.
|
||||
NonResourceURLs []string
|
||||
}
|
||||
|
|
|
@ -50,6 +50,10 @@ func ValidateSelfSubjectAccessReviewSpec(spec authorizationapi.SelfSubjectAccess
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func ValidateSelfSubjectRulesReview(review *authorizationapi.SelfSubjectRulesReview) field.ErrorList {
|
||||
return field.ErrorList{}
|
||||
}
|
||||
|
||||
func ValidateSubjectAccessReview(sar *authorizationapi.SubjectAccessReview) field.ErrorList {
|
||||
allErrs := ValidateSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec"))
|
||||
if !apiequality.Semantic.DeepEqual(metav1.ObjectMeta{}, sar.ObjectMeta) {
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
api "k8s.io/kubernetes/pkg/apis/abac"
|
||||
_ "k8s.io/kubernetes/pkg/apis/abac/latest"
|
||||
|
@ -114,7 +115,7 @@ func NewFromFile(path string) (policyList, error) {
|
|||
}
|
||||
|
||||
func matches(p api.Policy, a authorizer.Attributes) bool {
|
||||
if subjectMatches(p, a) {
|
||||
if subjectMatches(p, a.GetUser()) {
|
||||
if verbMatches(p, a) {
|
||||
// Resource and non-resource requests are mutually exclusive, at most one will match a policy
|
||||
if resourceMatches(p, a) {
|
||||
|
@ -129,15 +130,14 @@ func matches(p api.Policy, a authorizer.Attributes) bool {
|
|||
}
|
||||
|
||||
// subjectMatches returns true if specified user and group properties in the policy match the attributes
|
||||
func subjectMatches(p api.Policy, a authorizer.Attributes) bool {
|
||||
func subjectMatches(p api.Policy, user user.Info) bool {
|
||||
matched := false
|
||||
|
||||
username := ""
|
||||
groups := []string{}
|
||||
if user := a.GetUser(); user != nil {
|
||||
username = user.GetName()
|
||||
groups = user.GetGroups()
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
username := user.GetName()
|
||||
groups := user.GetGroups()
|
||||
|
||||
// If the policy specified a user, ensure it matches
|
||||
if len(p.Spec.User) > 0 {
|
||||
|
@ -232,3 +232,42 @@ func (pl policyList) Authorize(a authorizer.Attributes) (bool, string, error) {
|
|||
// policy file, compared to other steps such as encoding/decoding.
|
||||
// Then, add Caching only if needed.
|
||||
}
|
||||
|
||||
func (pl policyList) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
var (
|
||||
resourceRules []authorizer.ResourceRuleInfo
|
||||
nonResourceRules []authorizer.NonResourceRuleInfo
|
||||
)
|
||||
|
||||
for _, p := range pl {
|
||||
if subjectMatches(*p, user) {
|
||||
if p.Spec.Namespace == "*" || p.Spec.Namespace == namespace {
|
||||
if len(p.Spec.Resource) > 0 {
|
||||
r := authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: getVerbs(p.Spec.Readonly),
|
||||
APIGroups: []string{p.Spec.APIGroup},
|
||||
Resources: []string{p.Spec.Resource},
|
||||
}
|
||||
var resourceRule authorizer.ResourceRuleInfo = &r
|
||||
resourceRules = append(resourceRules, resourceRule)
|
||||
}
|
||||
if len(p.Spec.NonResourcePath) > 0 {
|
||||
r := authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: getVerbs(p.Spec.Readonly),
|
||||
NonResourceURLs: []string{p.Spec.NonResourcePath},
|
||||
}
|
||||
var nonResourceRule authorizer.NonResourceRuleInfo = &r
|
||||
nonResourceRules = append(nonResourceRules, nonResourceRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resourceRules, nonResourceRules, false, nil
|
||||
}
|
||||
|
||||
func getVerbs(isReadOnly bool) []string {
|
||||
if isReadOnly {
|
||||
return []string{"get", "list", "watch"}
|
||||
}
|
||||
return []string{"*"}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package abac
|
|||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -141,6 +142,203 @@ func TestAuthorizeV0(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizer.DefaultResourceRuleInfo {
|
||||
rules := make([]authorizer.DefaultResourceRuleInfo, len(infos))
|
||||
for i, info := range infos {
|
||||
rules[i] = authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: info.GetVerbs(),
|
||||
APIGroups: info.GetAPIGroups(),
|
||||
Resources: info.GetResources(),
|
||||
ResourceNames: info.GetResourceNames(),
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.DefaultNonResourceRuleInfo {
|
||||
rules := make([]authorizer.DefaultNonResourceRuleInfo, len(infos))
|
||||
for i, info := range infos {
|
||||
rules[i] = authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: info.GetVerbs(),
|
||||
NonResourceURLs: info.GetNonResourceURLs(),
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func TestRulesFor(t *testing.T) {
|
||||
a, err := newWithContents(t, `
|
||||
{ "readonly": true, "resource": "events" }
|
||||
{"user":"scheduler", "readonly": true, "resource": "pods" }
|
||||
{"user":"scheduler", "resource": "bindings" }
|
||||
{"user":"kubelet", "readonly": true, "resource": "pods" }
|
||||
{"user":"kubelet", "resource": "events" }
|
||||
{"user":"alice", "namespace": "projectCaribou"}
|
||||
{"user":"bob", "readonly": true, "namespace": "projectCaribou"}
|
||||
{"user":"bob", "readonly": true, "nonResourcePath": "*"}
|
||||
{"group":"a", "resource": "bindings" }
|
||||
{"group":"b", "readonly": true, "nonResourcePath": "*"}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read policy file: %v", err)
|
||||
}
|
||||
|
||||
authenticatedGroup := []string{user.AllAuthenticated}
|
||||
|
||||
uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1", Groups: authenticatedGroup}
|
||||
uKubelet := user.DefaultInfo{Name: "kubelet", UID: "uid2", Groups: []string{"a", "b"}}
|
||||
uAlice := user.DefaultInfo{Name: "alice", UID: "uid3", Groups: authenticatedGroup}
|
||||
uBob := user.DefaultInfo{Name: "bob", UID: "uid4", Groups: authenticatedGroup}
|
||||
uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5", Groups: []string{"a", "b"}}
|
||||
|
||||
testCases := []struct {
|
||||
User user.DefaultInfo
|
||||
Namespace string
|
||||
ExpectResourceRules []authorizer.DefaultResourceRuleInfo
|
||||
ExpectNonResourceRules []authorizer.DefaultNonResourceRuleInfo
|
||||
}{
|
||||
{
|
||||
User: uScheduler,
|
||||
Namespace: "ns1",
|
||||
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"events"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"bindings"},
|
||||
},
|
||||
},
|
||||
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{},
|
||||
},
|
||||
{
|
||||
User: uKubelet,
|
||||
Namespace: "ns1",
|
||||
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"events"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"bindings"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
},
|
||||
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
NonResourceURLs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
User: uAlice,
|
||||
Namespace: "projectCaribou",
|
||||
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"events"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
},
|
||||
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{},
|
||||
},
|
||||
{
|
||||
User: uBob,
|
||||
Namespace: "projectCaribou",
|
||||
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"events"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
},
|
||||
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
NonResourceURLs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
User: uChuck,
|
||||
Namespace: "ns1",
|
||||
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"bindings"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
},
|
||||
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
NonResourceURLs: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
attr := authorizer.AttributesRecord{
|
||||
User: &tc.User,
|
||||
Namespace: tc.Namespace,
|
||||
}
|
||||
resourceRules, nonResourceRules, _, _ := a.RulesFor(attr.GetUser(), attr.GetNamespace())
|
||||
actualResourceRules := getResourceRules(resourceRules)
|
||||
if !reflect.DeepEqual(tc.ExpectResourceRules, actualResourceRules) {
|
||||
t.Logf("tc: %v -> attr %v", tc, attr)
|
||||
t.Errorf("%d: Expected: \n%#v\n but actual: \n%#v\n",
|
||||
i, tc.ExpectResourceRules, actualResourceRules)
|
||||
}
|
||||
actualNonResourceRules := getNonResourceRules(nonResourceRules)
|
||||
if !reflect.DeepEqual(tc.ExpectNonResourceRules, actualNonResourceRules) {
|
||||
t.Logf("tc: %v -> attr %v", tc, attr)
|
||||
t.Errorf("%d: Expected: \n%#v\n but actual: \n%#v\n",
|
||||
i, tc.ExpectNonResourceRules, actualNonResourceRules)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizeV1beta1(t *testing.T) {
|
||||
a, err := newWithContents(t,
|
||||
`
|
||||
|
@ -609,7 +807,7 @@ func TestSubjectMatches(t *testing.T) {
|
|||
attr := authorizer.AttributesRecord{
|
||||
User: &tc.User,
|
||||
}
|
||||
actualMatch := subjectMatches(*policy, attr)
|
||||
actualMatch := subjectMatches(*policy, attr.GetUser())
|
||||
if tc.ExpectMatch != actualMatch {
|
||||
t.Errorf("%v: Expected actorMatches=%v but actually got=%v",
|
||||
k, tc.ExpectMatch, actualMatch)
|
||||
|
@ -617,7 +815,7 @@ func TestSubjectMatches(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func newWithContents(t *testing.T, contents string) (authorizer.Authorizer, error) {
|
||||
func newWithContents(t *testing.T, contents string) (policyList, error) {
|
||||
f, err := ioutil.TempFile("", "abac_test")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating policyfile: %v", err)
|
||||
|
|
|
@ -359,6 +359,7 @@ var ignoredResources = map[schema.GroupResource]struct{}{
|
|||
{Group: "authorization.k8s.io", Resource: "subjectaccessreviews"}: {},
|
||||
{Group: "authorization.k8s.io", Resource: "selfsubjectaccessreviews"}: {},
|
||||
{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: {},
|
||||
{Group: "authorization.k8s.io", Resource: "selfsubjectrulesreviews"}: {},
|
||||
{Group: "apiregistration.k8s.io", Resource: "apiservices"}: {},
|
||||
{Group: "apiextensions.k8s.io", Resource: "customresourcedefinitions"}: {},
|
||||
}
|
||||
|
|
|
@ -56,17 +56,20 @@ type AuthorizationConfig struct {
|
|||
|
||||
// New returns the right sort of union of multiple authorizer.Authorizer objects
|
||||
// based on the authorizationMode or an error.
|
||||
func (config AuthorizationConfig) New() (authorizer.Authorizer, error) {
|
||||
func (config AuthorizationConfig) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
|
||||
if len(config.AuthorizationModes) == 0 {
|
||||
return nil, errors.New("At least one authorization mode should be passed")
|
||||
return nil, nil, errors.New("At least one authorization mode should be passed")
|
||||
}
|
||||
|
||||
var authorizers []authorizer.Authorizer
|
||||
var (
|
||||
authorizers []authorizer.Authorizer
|
||||
ruleResolvers []authorizer.RuleResolver
|
||||
)
|
||||
authorizerMap := make(map[string]bool)
|
||||
|
||||
for _, authorizationMode := range config.AuthorizationModes {
|
||||
if authorizerMap[authorizationMode] {
|
||||
return nil, fmt.Errorf("Authorization mode %s specified more than once", authorizationMode)
|
||||
return nil, nil, fmt.Errorf("Authorization mode %s specified more than once", authorizationMode)
|
||||
}
|
||||
// Keep cases in sync with constant list above.
|
||||
switch authorizationMode {
|
||||
|
@ -81,29 +84,35 @@ func (config AuthorizationConfig) New() (authorizer.Authorizer, error) {
|
|||
authorizers = append(authorizers, nodeAuthorizer)
|
||||
|
||||
case modes.ModeAlwaysAllow:
|
||||
authorizers = append(authorizers, authorizerfactory.NewAlwaysAllowAuthorizer())
|
||||
alwaysAllowAuthorizer := authorizerfactory.NewAlwaysAllowAuthorizer()
|
||||
authorizers = append(authorizers, alwaysAllowAuthorizer)
|
||||
ruleResolvers = append(ruleResolvers, alwaysAllowAuthorizer)
|
||||
case modes.ModeAlwaysDeny:
|
||||
authorizers = append(authorizers, authorizerfactory.NewAlwaysDenyAuthorizer())
|
||||
alwaysDenyAuthorizer := authorizerfactory.NewAlwaysDenyAuthorizer()
|
||||
authorizers = append(authorizers, alwaysDenyAuthorizer)
|
||||
ruleResolvers = append(ruleResolvers, alwaysDenyAuthorizer)
|
||||
case modes.ModeABAC:
|
||||
if config.PolicyFile == "" {
|
||||
return nil, errors.New("ABAC's authorization policy file not passed")
|
||||
return nil, nil, errors.New("ABAC's authorization policy file not passed")
|
||||
}
|
||||
abacAuthorizer, err := abac.NewFromFile(config.PolicyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authorizers = append(authorizers, abacAuthorizer)
|
||||
ruleResolvers = append(ruleResolvers, abacAuthorizer)
|
||||
case modes.ModeWebhook:
|
||||
if config.WebhookConfigFile == "" {
|
||||
return nil, errors.New("Webhook's configuration file not passed")
|
||||
return nil, nil, errors.New("Webhook's configuration file not passed")
|
||||
}
|
||||
webhookAuthorizer, err := webhook.New(config.WebhookConfigFile,
|
||||
config.WebhookCacheAuthorizedTTL,
|
||||
config.WebhookCacheUnauthorizedTTL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
authorizers = append(authorizers, webhookAuthorizer)
|
||||
ruleResolvers = append(ruleResolvers, webhookAuthorizer)
|
||||
case modes.ModeRBAC:
|
||||
rbacAuthorizer := rbac.New(
|
||||
&rbac.RoleGetter{Lister: config.InformerFactory.Rbac().InternalVersion().Roles().Lister()},
|
||||
|
@ -112,18 +121,19 @@ func (config AuthorizationConfig) New() (authorizer.Authorizer, error) {
|
|||
&rbac.ClusterRoleBindingLister{Lister: config.InformerFactory.Rbac().InternalVersion().ClusterRoleBindings().Lister()},
|
||||
)
|
||||
authorizers = append(authorizers, rbacAuthorizer)
|
||||
ruleResolvers = append(ruleResolvers, rbacAuthorizer)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown authorization mode %s specified", authorizationMode)
|
||||
return nil, nil, fmt.Errorf("Unknown authorization mode %s specified", authorizationMode)
|
||||
}
|
||||
authorizerMap[authorizationMode] = true
|
||||
}
|
||||
|
||||
if !authorizerMap[modes.ModeABAC] && config.PolicyFile != "" {
|
||||
return nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC")
|
||||
return nil, nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC")
|
||||
}
|
||||
if !authorizerMap[modes.ModeWebhook] && config.WebhookConfigFile != "" {
|
||||
return nil, errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook")
|
||||
return nil, nil, errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook")
|
||||
}
|
||||
|
||||
return union.New(authorizers...), nil
|
||||
return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ func TestNew(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
_, err := tt.config.New()
|
||||
_, _, err := tt.config.New()
|
||||
if tt.wantErr && (err == nil) {
|
||||
t.Errorf("New %s", tt.msg)
|
||||
} else if !tt.wantErr && (err != nil) {
|
||||
|
|
|
@ -256,7 +256,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
|
|||
// handlers that we have.
|
||||
restStorageProviders := []RESTStorageProvider{
|
||||
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authenticator},
|
||||
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
||||
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer, RuleResolver: c.GenericConfig.RuleResolver},
|
||||
autoscalingrest.RESTStorageProvider{},
|
||||
batchrest.RESTStorageProvider{},
|
||||
certificatesrest.RESTStorageProvider{},
|
||||
|
|
|
@ -28,11 +28,13 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/authorization"
|
||||
"k8s.io/kubernetes/pkg/registry/authorization/localsubjectaccessreview"
|
||||
"k8s.io/kubernetes/pkg/registry/authorization/selfsubjectaccessreview"
|
||||
"k8s.io/kubernetes/pkg/registry/authorization/selfsubjectrulesreview"
|
||||
"k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview"
|
||||
)
|
||||
|
||||
type RESTStorageProvider struct {
|
||||
Authorizer authorizer.Authorizer
|
||||
Authorizer authorizer.Authorizer
|
||||
RuleResolver authorizer.RuleResolver
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) {
|
||||
|
@ -70,6 +72,9 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag
|
|||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("localsubjectaccessreviews")) {
|
||||
storage["localsubjectaccessreviews"] = localsubjectaccessreview.NewREST(p.Authorizer)
|
||||
}
|
||||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectrulesreviews")) {
|
||||
storage["selfsubjectrulesreviews"] = selfsubjectrulesreview.NewREST(p.RuleResolver)
|
||||
}
|
||||
|
||||
return storage
|
||||
}
|
||||
|
@ -87,6 +92,9 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API
|
|||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("localsubjectaccessreviews")) {
|
||||
storage["localsubjectaccessreviews"] = localsubjectaccessreview.NewREST(p.Authorizer)
|
||||
}
|
||||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectrulesreviews")) {
|
||||
storage["selfsubjectrulesreviews"] = selfsubjectrulesreview.NewREST(p.RuleResolver)
|
||||
}
|
||||
|
||||
return storage
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package selfsubjectrulesreview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
|
||||
)
|
||||
|
||||
// REST implements a RESTStorage for selfsubjectrulesreview.
|
||||
type REST struct {
|
||||
ruleResolver authorizer.RuleResolver
|
||||
}
|
||||
|
||||
// NewREST returns a RESTStorage object that will work against selfsubjectrulesreview.
|
||||
func NewREST(ruleResolver authorizer.RuleResolver) *REST {
|
||||
return &REST{ruleResolver}
|
||||
}
|
||||
|
||||
// New creates a new selfsubjectrulesreview object.
|
||||
func (r *REST) New() runtime.Object {
|
||||
return &authorizationapi.SelfSubjectRulesReview{}
|
||||
}
|
||||
|
||||
// Create attempts to get self subject rules in specific namespace.
|
||||
func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) {
|
||||
selfSRR, ok := obj.(*authorizationapi.SelfSubjectRulesReview)
|
||||
if !ok {
|
||||
return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectRulesReview: %#v", obj))
|
||||
}
|
||||
|
||||
user, ok := genericapirequest.UserFrom(ctx)
|
||||
if !ok {
|
||||
return nil, apierrors.NewBadRequest("no user present on request")
|
||||
}
|
||||
|
||||
namespace := selfSRR.Spec.Namespace
|
||||
if namespace == "" {
|
||||
return nil, apierrors.NewBadRequest("no namespace on request")
|
||||
}
|
||||
resourceInfo, nonResourceInfo, incomplete, err := r.ruleResolver.RulesFor(user, namespace)
|
||||
|
||||
ret := &authorizationapi.SelfSubjectRulesReview{
|
||||
Status: authorizationapi.SubjectRulesReviewStatus{
|
||||
ResourceRules: getResourceRules(resourceInfo),
|
||||
NonResourceRules: getNonResourceRules(nonResourceInfo),
|
||||
Incomplete: incomplete,
|
||||
},
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ret.Status.EvaluationError = err.Error()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizationapi.ResourceRule {
|
||||
rules := make([]authorizationapi.ResourceRule, len(infos))
|
||||
for i, info := range infos {
|
||||
rules[i] = authorizationapi.ResourceRule{
|
||||
Verbs: info.GetVerbs(),
|
||||
APIGroups: info.GetAPIGroups(),
|
||||
Resources: info.GetResources(),
|
||||
ResourceNames: info.GetResourceNames(),
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizationapi.NonResourceRule {
|
||||
rules := make([]authorizationapi.NonResourceRule, len(infos))
|
||||
for i, info := range infos {
|
||||
rules[i] = authorizationapi.NonResourceRule{
|
||||
Verbs: info.GetVerbs(),
|
||||
NonResourceURLs: info.GetNonResourceURLs(),
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
|
@ -123,6 +123,36 @@ func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (boo
|
|||
return false, reason, nil
|
||||
}
|
||||
|
||||
func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
var (
|
||||
resourceRules []authorizer.ResourceRuleInfo
|
||||
nonResourceRules []authorizer.NonResourceRuleInfo
|
||||
)
|
||||
|
||||
policyRules, err := r.authorizationRuleResolver.RulesFor(user, namespace)
|
||||
for _, policyRule := range policyRules {
|
||||
if len(policyRule.Resources) > 0 {
|
||||
r := authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: policyRule.Verbs,
|
||||
APIGroups: policyRule.APIGroups,
|
||||
Resources: policyRule.Resources,
|
||||
ResourceNames: policyRule.ResourceNames,
|
||||
}
|
||||
var resourceRule authorizer.ResourceRuleInfo = &r
|
||||
resourceRules = append(resourceRules, resourceRule)
|
||||
}
|
||||
if len(policyRule.NonResourceURLs) > 0 {
|
||||
r := authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: policyRule.Verbs,
|
||||
NonResourceURLs: policyRule.NonResourceURLs,
|
||||
}
|
||||
var nonResourceRule authorizer.NonResourceRuleInfo = &r
|
||||
nonResourceRules = append(nonResourceRules, nonResourceRule)
|
||||
}
|
||||
}
|
||||
return resourceRules, nonResourceRules, false, err
|
||||
}
|
||||
|
||||
func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer {
|
||||
authorizer := &RBACAuthorizer{
|
||||
authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(
|
||||
|
|
|
@ -44,6 +44,7 @@ var (
|
|||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&SelfSubjectRulesReview{},
|
||||
&SelfSubjectAccessReview{},
|
||||
&SubjectAccessReview{},
|
||||
&LocalSubjectAccessReview{},
|
||||
|
|
|
@ -180,3 +180,82 @@ type SubjectAccessReviewStatus struct {
|
|||
// +optional
|
||||
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,3,opt,name=evaluationError"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:noVerbs
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace.
|
||||
// The returned list of actions may be incomplete depending on the server's authorization mode,
|
||||
// and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions,
|
||||
// or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to
|
||||
// drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns.
|
||||
// SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.
|
||||
type SelfSubjectRulesReview struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Spec holds information about the request being evaluated.
|
||||
Spec SelfSubjectRulesReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
|
||||
|
||||
// Status is filled in by the server and indicates the set of actions a user can perform.
|
||||
// +optional
|
||||
Status SubjectRulesReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
type SelfSubjectRulesReviewSpec struct {
|
||||
// Namespace to evaluate rules for. Required.
|
||||
Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"`
|
||||
}
|
||||
|
||||
// SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on
|
||||
// the set of authorizers the server is configured with and any errors experienced during evaluation.
|
||||
// Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission,
|
||||
// even if that list is incomplete.
|
||||
type SubjectRulesReviewStatus struct {
|
||||
// ResourceRules is the list of actions the subject is allowed to perform on resources.
|
||||
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
|
||||
ResourceRules []ResourceRule `json:"resourceRules" protobuf:"bytes,1,rep,name=resourceRules"`
|
||||
// NonResourceRules is the list of actions the subject is allowed to perform on non-resources.
|
||||
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
|
||||
NonResourceRules []NonResourceRule `json:"nonResourceRules" protobuf:"bytes,2,rep,name=nonResourceRules"`
|
||||
// Incomplete is true when the rules returned by this call are incomplete. This is most commonly
|
||||
// encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.
|
||||
Incomplete bool `json:"incomplete" protobuf:"bytes,3,rep,name=incomplete"`
|
||||
// EvaluationError can appear in combination with Rules. It indicates an error occurred during
|
||||
// rule evaluation, such as an authorizer that doesn't support rule evaluation, and that
|
||||
// ResourceRules and/or NonResourceRules may be incomplete.
|
||||
// +optional
|
||||
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,4,opt,name=evaluationError"`
|
||||
}
|
||||
|
||||
// ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant,
|
||||
// may contain duplicates, and possibly be incomplete.
|
||||
type ResourceRule struct {
|
||||
// Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all.
|
||||
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
|
||||
|
||||
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
|
||||
// the enumerated resources in any API group will be allowed. "*" means all.
|
||||
// +optional
|
||||
APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"`
|
||||
// Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.
|
||||
// +optional
|
||||
Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"`
|
||||
// ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all.
|
||||
// +optional
|
||||
ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,4,rep,name=resourceNames"`
|
||||
}
|
||||
|
||||
// NonResourceRule holds information that describes a rule for the non-resource
|
||||
type NonResourceRule struct {
|
||||
// Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all.
|
||||
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
|
||||
|
||||
// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full,
|
||||
// final step in the path. "*" means all.
|
||||
// +optional
|
||||
NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,2,rep,name=nonResourceURLs"`
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ var (
|
|||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&SelfSubjectRulesReview{},
|
||||
&SelfSubjectAccessReview{},
|
||||
&SubjectAccessReview{},
|
||||
&LocalSubjectAccessReview{},
|
||||
|
|
|
@ -180,3 +180,82 @@ type SubjectAccessReviewStatus struct {
|
|||
// +optional
|
||||
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,3,opt,name=evaluationError"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:noVerbs
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace.
|
||||
// The returned list of actions may be incomplete depending on the server's authorization mode,
|
||||
// and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions,
|
||||
// or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to
|
||||
// drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns.
|
||||
// SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.
|
||||
type SelfSubjectRulesReview struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Spec holds information about the request being evaluated.
|
||||
Spec SelfSubjectRulesReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
|
||||
|
||||
// Status is filled in by the server and indicates the set of actions a user can perform.
|
||||
// +optional
|
||||
Status SubjectRulesReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
type SelfSubjectRulesReviewSpec struct {
|
||||
// Namespace to evaluate rules for. Required.
|
||||
Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"`
|
||||
}
|
||||
|
||||
// SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on
|
||||
// the set of authorizers the server is configured with and any errors experienced during evaluation.
|
||||
// Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission,
|
||||
// even if that list is incomplete.
|
||||
type SubjectRulesReviewStatus struct {
|
||||
// ResourceRules is the list of actions the subject is allowed to perform on resources.
|
||||
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
|
||||
ResourceRules []ResourceRule `json:"resourceRules" protobuf:"bytes,1,rep,name=resourceRules"`
|
||||
// NonResourceRules is the list of actions the subject is allowed to perform on non-resources.
|
||||
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
|
||||
NonResourceRules []NonResourceRule `json:"nonResourceRules" protobuf:"bytes,2,rep,name=nonResourceRules"`
|
||||
// Incomplete is true when the rules returned by this call are incomplete. This is most commonly
|
||||
// encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.
|
||||
Incomplete bool `json:"incomplete" protobuf:"bytes,3,rep,name=incomplete"`
|
||||
// EvaluationError can appear in combination with Rules. It indicates an error occurred during
|
||||
// rule evaluation, such as an authorizer that doesn't support rule evaluation, and that
|
||||
// ResourceRules and/or NonResourceRules may be incomplete.
|
||||
// +optional
|
||||
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,4,opt,name=evaluationError"`
|
||||
}
|
||||
|
||||
// ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant,
|
||||
// may contain duplicates, and possibly be incomplete.
|
||||
type ResourceRule struct {
|
||||
// Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all.
|
||||
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
|
||||
|
||||
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
|
||||
// the enumerated resources in any API group will be allowed. "*" means all.
|
||||
// +optional
|
||||
APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"`
|
||||
// Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.
|
||||
// +optional
|
||||
Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"`
|
||||
// ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all.
|
||||
// +optional
|
||||
ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,4,rep,name=resourceNames"`
|
||||
}
|
||||
|
||||
// NonResourceRule holds information that describes a rule for the non-resource
|
||||
type NonResourceRule struct {
|
||||
// Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all.
|
||||
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
|
||||
|
||||
// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full,
|
||||
// final step in the path. "*" means all.
|
||||
// +optional
|
||||
NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,2,rep,name=nonResourceURLs"`
|
||||
}
|
||||
|
|
|
@ -76,6 +76,12 @@ func (f AuthorizerFunc) Authorize(a Attributes) (bool, string, error) {
|
|||
return f(a)
|
||||
}
|
||||
|
||||
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
|
||||
type RuleResolver interface {
|
||||
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
|
||||
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
|
||||
}
|
||||
|
||||
// RequestAttributesGetter provides a function that extracts Attributes from an http.Request
|
||||
type RequestAttributesGetter interface {
|
||||
GetRequestAttributes(user.Info, *http.Request) Attributes
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package authorizer
|
||||
|
||||
type ResourceRuleInfo interface {
|
||||
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||
GetVerbs() []string
|
||||
// GetAPIGroups return the names of the APIGroup that contains the resources.
|
||||
GetAPIGroups() []string
|
||||
// GetResources return a list of resources the rule applies to.
|
||||
GetResources() []string
|
||||
// GetResourceNames return a white list of names that the rule applies to.
|
||||
GetResourceNames() []string
|
||||
}
|
||||
|
||||
// DefaultResourceRuleInfo holds information that describes a rule for the resource
|
||||
type DefaultResourceRuleInfo struct {
|
||||
Verbs []string
|
||||
APIGroups []string
|
||||
Resources []string
|
||||
ResourceNames []string
|
||||
}
|
||||
|
||||
func (i *DefaultResourceRuleInfo) GetVerbs() []string {
|
||||
return i.Verbs
|
||||
}
|
||||
|
||||
func (i *DefaultResourceRuleInfo) GetAPIGroups() []string {
|
||||
return i.APIGroups
|
||||
}
|
||||
|
||||
func (i *DefaultResourceRuleInfo) GetResources() []string {
|
||||
return i.Resources
|
||||
}
|
||||
|
||||
func (i *DefaultResourceRuleInfo) GetResourceNames() []string {
|
||||
return i.ResourceNames
|
||||
}
|
||||
|
||||
type NonResourceRuleInfo interface {
|
||||
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||
GetVerbs() []string
|
||||
// GetNonResourceURLs return a set of partial urls that a user should have access to.
|
||||
GetNonResourceURLs() []string
|
||||
}
|
||||
|
||||
// DefaultNonResourceRuleInfo holds information that describes a rule for the non-resource
|
||||
type DefaultNonResourceRuleInfo struct {
|
||||
Verbs []string
|
||||
NonResourceURLs []string
|
||||
}
|
||||
|
||||
func (i *DefaultNonResourceRuleInfo) GetVerbs() []string {
|
||||
return i.Verbs
|
||||
}
|
||||
|
||||
func (i *DefaultNonResourceRuleInfo) GetNonResourceURLs() []string {
|
||||
return i.NonResourceURLs
|
||||
}
|
|
@ -19,6 +19,7 @@ package authorizerfactory
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
|
@ -31,7 +32,22 @@ func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (authorized bool
|
|||
return true, "", nil
|
||||
}
|
||||
|
||||
func NewAlwaysAllowAuthorizer() authorizer.Authorizer {
|
||||
func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
return []authorizer.ResourceRuleInfo{
|
||||
&authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
}, []authorizer.NonResourceRuleInfo{
|
||||
&authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: []string{"*"},
|
||||
NonResourceURLs: []string{"*"},
|
||||
},
|
||||
}, false, nil
|
||||
}
|
||||
|
||||
func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer {
|
||||
return new(alwaysAllowAuthorizer)
|
||||
}
|
||||
|
||||
|
@ -44,7 +60,11 @@ func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (authorized bool,
|
|||
return false, "Everything is forbidden.", nil
|
||||
}
|
||||
|
||||
func NewAlwaysDenyAuthorizer() authorizer.Authorizer {
|
||||
func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, nil
|
||||
}
|
||||
|
||||
func NewAlwaysDenyAuthorizer() *alwaysDenyAuthorizer {
|
||||
return new(alwaysDenyAuthorizer)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"strings"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
|
@ -55,3 +56,40 @@ func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (bool,
|
|||
|
||||
return false, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
|
||||
}
|
||||
|
||||
// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver
|
||||
type unionAuthzRulesHandler []authorizer.RuleResolver
|
||||
|
||||
// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
|
||||
func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver {
|
||||
return unionAuthzRulesHandler(authorizationHandlers)
|
||||
}
|
||||
|
||||
// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
|
||||
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
var (
|
||||
errList []error
|
||||
resourceRulesList []authorizer.ResourceRuleInfo
|
||||
nonResourceRulesList []authorizer.NonResourceRuleInfo
|
||||
)
|
||||
incompleteStatus := false
|
||||
|
||||
for _, currAuthzHandler := range authzHandler {
|
||||
resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace)
|
||||
|
||||
if incomplete == true {
|
||||
incompleteStatus = true
|
||||
}
|
||||
if err != nil {
|
||||
errList = append(errList, err)
|
||||
}
|
||||
if len(resourceRules) > 0 {
|
||||
resourceRulesList = append(resourceRulesList, resourceRules...)
|
||||
}
|
||||
if len(nonResourceRules) > 0 {
|
||||
nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...)
|
||||
}
|
||||
}
|
||||
|
||||
return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList)
|
||||
}
|
||||
|
|
|
@ -18,8 +18,10 @@ package union
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
|
@ -81,3 +83,143 @@ func TestAuthorizationError(t *testing.T) {
|
|||
t.Errorf("Expected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type mockAuthzRuleHandler struct {
|
||||
resourceRules []authorizer.ResourceRuleInfo
|
||||
nonResourceRules []authorizer.NonResourceRuleInfo
|
||||
err error
|
||||
}
|
||||
|
||||
func (mock *mockAuthzRuleHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
if mock.err != nil {
|
||||
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, mock.err
|
||||
}
|
||||
return mock.resourceRules, mock.nonResourceRules, false, nil
|
||||
}
|
||||
|
||||
func TestAuthorizationResourceRules(t *testing.T) {
|
||||
handler1 := &mockAuthzRuleHandler{
|
||||
resourceRules: []authorizer.ResourceRuleInfo{
|
||||
&authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"bindings"},
|
||||
},
|
||||
&authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
handler2 := &mockAuthzRuleHandler{
|
||||
resourceRules: []authorizer.ResourceRuleInfo{
|
||||
&authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"events"},
|
||||
},
|
||||
&authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: []string{"get"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
ResourceNames: []string{"foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []authorizer.DefaultResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"bindings"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"events"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get"},
|
||||
APIGroups: []string{"*"},
|
||||
Resources: []string{"*"},
|
||||
ResourceNames: []string{"foo"},
|
||||
},
|
||||
}
|
||||
|
||||
authzRulesHandler := NewRuleResolvers(handler1, handler2)
|
||||
|
||||
rules, _, _, _ := authzRulesHandler.RulesFor(nil, "")
|
||||
actual := getResourceRules(rules)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationNonResourceRules(t *testing.T) {
|
||||
handler1 := &mockAuthzRuleHandler{
|
||||
nonResourceRules: []authorizer.NonResourceRuleInfo{
|
||||
&authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{"/api"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handler2 := &mockAuthzRuleHandler{
|
||||
nonResourceRules: []authorizer.NonResourceRuleInfo{
|
||||
&authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{"/api/*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []authorizer.DefaultNonResourceRuleInfo{
|
||||
{
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{"/api"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get"},
|
||||
NonResourceURLs: []string{"/api/*"},
|
||||
},
|
||||
}
|
||||
|
||||
authzRulesHandler := NewRuleResolvers(handler1, handler2)
|
||||
|
||||
_, rules, _, _ := authzRulesHandler.RulesFor(nil, "")
|
||||
actual := getNonResourceRules(rules)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizer.DefaultResourceRuleInfo {
|
||||
rules := make([]authorizer.DefaultResourceRuleInfo, len(infos))
|
||||
for i, info := range infos {
|
||||
rules[i] = authorizer.DefaultResourceRuleInfo{
|
||||
Verbs: info.GetVerbs(),
|
||||
APIGroups: info.GetAPIGroups(),
|
||||
Resources: info.GetResources(),
|
||||
ResourceNames: info.GetResourceNames(),
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.DefaultNonResourceRuleInfo {
|
||||
rules := make([]authorizer.DefaultNonResourceRuleInfo, len(infos))
|
||||
for i, info := range infos {
|
||||
rules[i] = authorizer.DefaultNonResourceRuleInfo{
|
||||
Verbs: info.GetVerbs(),
|
||||
NonResourceURLs: info.GetNonResourceURLs(),
|
||||
}
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
|
|
@ -87,6 +87,9 @@ type Config struct {
|
|||
// Authorizer determines whether the subject is allowed to make the request based only
|
||||
// on the RequestURI
|
||||
Authorizer authorizer.Authorizer
|
||||
// RuleResolver is required to get the list of rules that apply to a given user
|
||||
// in a given namespace
|
||||
RuleResolver authorizer.RuleResolver
|
||||
// AdmissionControl performs deep inspection of a given request (including content)
|
||||
// to set values and determine whether its allowed
|
||||
AdmissionControl admission.Interface
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
|
@ -196,6 +197,16 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo
|
|||
return r.Status.Allowed, r.Status.Reason, nil
|
||||
}
|
||||
|
||||
//TODO: need to finish the method to get the rules when using webhook mode
|
||||
func (w *WebhookAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||
var (
|
||||
resourceRules []authorizer.ResourceRuleInfo
|
||||
nonResourceRules []authorizer.NonResourceRuleInfo
|
||||
)
|
||||
incomplete := true
|
||||
return resourceRules, nonResourceRules, incomplete, fmt.Errorf("webhook authorizer does not support user rule resolution")
|
||||
}
|
||||
|
||||
func convertToSARExtra(extra map[string][]string) map[string]authorization.ExtraValue {
|
||||
if extra == nil {
|
||||
return nil
|
||||
|
|
|
@ -74,7 +74,7 @@ func TestNodeAuthorizer(t *testing.T) {
|
|||
AuthorizationModes: []string{"Node", "RBAC"},
|
||||
InformerFactory: informerFactory,
|
||||
}
|
||||
nodeRBACAuthorizer, err := authorizerConfig.New()
|
||||
nodeRBACAuthorizer, _, err := authorizerConfig.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -394,6 +394,8 @@ var ephemeralWhiteList = createEphemeralWhiteList(
|
|||
|
||||
// k8s.io/kubernetes/pkg/apis/authorization/v1beta1
|
||||
|
||||
// SRR objects that are not stored in etcd
|
||||
gvr("authorization.k8s.io", "v1beta1", "selfsubjectrulesreviews"),
|
||||
// SAR objects that are not stored in etcd
|
||||
gvr("authorization.k8s.io", "v1beta1", "selfsubjectaccessreviews"),
|
||||
gvr("authorization.k8s.io", "v1beta1", "localsubjectaccessreviews"),
|
||||
|
@ -402,6 +404,8 @@ var ephemeralWhiteList = createEphemeralWhiteList(
|
|||
|
||||
// k8s.io/kubernetes/pkg/apis/authorization/v1
|
||||
|
||||
// SRR objects that are not stored in etcd
|
||||
gvr("authorization.k8s.io", "v1", "selfsubjectrulesreviews"),
|
||||
// SAR objects that are not stored in etcd
|
||||
gvr("authorization.k8s.io", "v1", "selfsubjectaccessreviews"),
|
||||
gvr("authorization.k8s.io", "v1", "localsubjectaccessreviews"),
|
||||
|
|
Loading…
Reference in New Issue