| ... | ... |
@@ -18,6 +18,8 @@ func init() {
|
| 18 | 18 |
&PolicyBindingList{},
|
| 19 | 19 |
&RoleBindingList{},
|
| 20 | 20 |
&RoleList{},
|
| 21 |
+ |
|
| 22 |
+ &IsPersonalSubjectAccessReview{},
|
|
| 21 | 23 |
) |
| 22 | 24 |
} |
| 23 | 25 |
|
| ... | ... |
@@ -33,3 +35,5 @@ func (*PolicyList) IsAnAPIObject() {}
|
| 33 | 33 |
func (*PolicyBindingList) IsAnAPIObject() {}
|
| 34 | 34 |
func (*RoleBindingList) IsAnAPIObject() {}
|
| 35 | 35 |
func (*RoleList) IsAnAPIObject() {}
|
| 36 |
+ |
|
| 37 |
+func (*IsPersonalSubjectAccessReview) IsAnAPIObject() {}
|
| ... | ... |
@@ -83,6 +83,11 @@ type PolicyRule struct {
|
| 83 | 83 |
NonResourceURLs kutil.StringSet |
| 84 | 84 |
} |
| 85 | 85 |
|
| 86 |
+// IsPersonalSubjectAccessReview is a marker for PolicyRule.AttributeRestrictions that denotes that subjectaccessreviews on self should be allowed |
|
| 87 |
+type IsPersonalSubjectAccessReview struct {
|
|
| 88 |
+ kapi.TypeMeta |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 86 | 91 |
// Role is a logical grouping of PolicyRules that can be referenced as a unit by RoleBindings. |
| 87 | 92 |
type Role struct {
|
| 88 | 93 |
kapi.TypeMeta |
| ... | ... |
@@ -18,6 +18,8 @@ func init() {
|
| 18 | 18 |
&PolicyBindingList{},
|
| 19 | 19 |
&RoleBindingList{},
|
| 20 | 20 |
&RoleList{},
|
| 21 |
+ |
|
| 22 |
+ &IsPersonalSubjectAccessReview{},
|
|
| 21 | 23 |
) |
| 22 | 24 |
} |
| 23 | 25 |
|
| ... | ... |
@@ -33,3 +35,5 @@ func (*PolicyList) IsAnAPIObject() {}
|
| 33 | 33 |
func (*PolicyBindingList) IsAnAPIObject() {}
|
| 34 | 34 |
func (*RoleBindingList) IsAnAPIObject() {}
|
| 35 | 35 |
func (*RoleList) IsAnAPIObject() {}
|
| 36 |
+ |
|
| 37 |
+func (*IsPersonalSubjectAccessReview) IsAnAPIObject() {}
|
| ... | ... |
@@ -20,7 +20,7 @@ type PolicyRule struct {
|
| 20 | 20 |
Verbs []string `json:"verbs"` |
| 21 | 21 |
// AttributeRestrictions will vary depending on what the Authorizer/AuthorizationAttributeBuilder pair supports. |
| 22 | 22 |
// If the Authorizer does not recognize how to handle the AttributeRestrictions, the Authorizer should report an error. |
| 23 |
- AttributeRestrictions kruntime.RawExtension `json:"attributeRestrictions"` |
|
| 23 |
+ AttributeRestrictions kruntime.RawExtension `json:"attributeRestrictions,omitempty"` |
|
| 24 | 24 |
// ResourceKinds is a list of resources this rule applies to. ResourceAll represents all resources. |
| 25 | 25 |
// DEPRECATED |
| 26 | 26 |
ResourceKinds []string `json:"resourceKinds,omitempty"` |
| ... | ... |
@@ -33,6 +33,11 @@ type PolicyRule struct {
|
| 33 | 33 |
NonResourceURLsSlice []string `json:"nonResourceURLs,omitempty"` |
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 |
+// IsPersonalSubjectAccessReview is a marker for PolicyRule.AttributeRestrictions that denotes that subjectaccessreviews on self should be allowed |
|
| 37 |
+type IsPersonalSubjectAccessReview struct {
|
|
| 38 |
+ kapi.TypeMeta `json:",inline"` |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 36 | 41 |
// Role is a logical grouping of PolicyRules that can be referenced as a unit by RoleBindings. |
| 37 | 42 |
type Role struct {
|
| 38 | 43 |
kapi.TypeMeta `json:",inline"` |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package authorizer |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 4 | 5 |
"path" |
| 5 | 6 |
"strings" |
| 6 | 7 |
|
| ... | ... |
@@ -34,6 +35,16 @@ func (a DefaultAuthorizationAttributes) RuleMatches(rule authorizationapi.Policy |
| 34 | 34 |
|
| 35 | 35 |
if a.resourceMatches(allowedResourceTypes) {
|
| 36 | 36 |
if a.nameMatches(rule.ResourceNames) {
|
| 37 |
+ // this rule matches the request, so we should check the additional restrictions to be sure that it's allowed |
|
| 38 |
+ if rule.AttributeRestrictions.Object != nil {
|
|
| 39 |
+ switch rule.AttributeRestrictions.Object.(type) {
|
|
| 40 |
+ case (*authorizationapi.IsPersonalSubjectAccessReview): |
|
| 41 |
+ return IsPersonalAccessReview(a) |
|
| 42 |
+ default: |
|
| 43 |
+ return false, fmt.Errorf("unable to interpret: %#v", rule.AttributeRestrictions.Object)
|
|
| 44 |
+ } |
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 37 | 47 |
return true, nil |
| 38 | 48 |
} |
| 39 | 49 |
} |
| ... | ... |
@@ -40,7 +40,7 @@ func (a *openshiftAuthorizationAttributeBuilder) GetAttributes(req *http.Request |
| 40 | 40 |
Verb: requestInfo.Verb, |
| 41 | 41 |
Resource: requestInfo.Resource, |
| 42 | 42 |
ResourceName: requestInfo.Name, |
| 43 |
- RequestAttributes: nil, |
|
| 43 |
+ RequestAttributes: req, |
|
| 44 | 44 |
NonResourceURL: false, |
| 45 | 45 |
URL: req.URL.Path, |
| 46 | 46 |
}, nil |
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
|
| 6 | 6 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
| 7 | 7 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" |
| 8 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 8 | 9 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
| 9 | 10 |
|
| 10 | 11 |
authorizationapi "github.com/openshift/origin/pkg/authorization/api" |
| ... | ... |
@@ -12,6 +13,40 @@ import ( |
| 12 | 12 |
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" |
| 13 | 13 |
) |
| 14 | 14 |
|
| 15 |
+func TestInvalidRole(t *testing.T) {
|
|
| 16 |
+ test := &authorizeTest{
|
|
| 17 |
+ context: kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "mallet"), &user.DefaultInfo{Name: "Brad"}),
|
|
| 18 |
+ attributes: &DefaultAuthorizationAttributes{
|
|
| 19 |
+ Verb: "get", |
|
| 20 |
+ Resource: "buildConfigs", |
|
| 21 |
+ }, |
|
| 22 |
+ expectedAllowed: false, |
|
| 23 |
+ expectedError: "unable to interpret:", |
|
| 24 |
+ } |
|
| 25 |
+ test.policies = newDefaultGlobalPolicies() |
|
| 26 |
+ test.policies = append(test.policies, newInvalidExtensionPolicies()...) |
|
| 27 |
+ test.bindings = newDefaultGlobalBinding() |
|
| 28 |
+ test.bindings = append(test.bindings, newInvalidExtensionBindings()...) |
|
| 29 |
+ |
|
| 30 |
+ test.test(t) |
|
| 31 |
+} |
|
| 32 |
+func TestInvalidRoleButRuleNotUsed(t *testing.T) {
|
|
| 33 |
+ test := &authorizeTest{
|
|
| 34 |
+ context: kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "mallet"), &user.DefaultInfo{Name: "Brad"}),
|
|
| 35 |
+ attributes: &DefaultAuthorizationAttributes{
|
|
| 36 |
+ Verb: "update", |
|
| 37 |
+ Resource: "buildConfigs", |
|
| 38 |
+ }, |
|
| 39 |
+ expectedAllowed: true, |
|
| 40 |
+ expectedReason: "allowed by rule in mallet", |
|
| 41 |
+ } |
|
| 42 |
+ test.policies = newDefaultGlobalPolicies() |
|
| 43 |
+ test.policies = append(test.policies, newInvalidExtensionPolicies()...) |
|
| 44 |
+ test.bindings = newDefaultGlobalBinding() |
|
| 45 |
+ test.bindings = append(test.bindings, newInvalidExtensionBindings()...) |
|
| 46 |
+ |
|
| 47 |
+ test.test(t) |
|
| 48 |
+} |
|
| 15 | 49 |
func TestViewerGetAllowedKindInMallet(t *testing.T) {
|
| 16 | 50 |
test := &authorizeTest{
|
| 17 | 51 |
context: kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), "mallet"), &user.DefaultInfo{Name: "Victor"}),
|
| ... | ... |
@@ -418,6 +453,57 @@ func newMalletBindings() []authorizationapi.PolicyBinding {
|
| 418 | 418 |
}, |
| 419 | 419 |
} |
| 420 | 420 |
} |
| 421 |
+func newInvalidExtensionPolicies() []authorizationapi.Policy {
|
|
| 422 |
+ return []authorizationapi.Policy{
|
|
| 423 |
+ {
|
|
| 424 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 425 |
+ Name: authorizationapi.PolicyName, |
|
| 426 |
+ Namespace: "mallet", |
|
| 427 |
+ }, |
|
| 428 |
+ Roles: map[string]authorizationapi.Role{
|
|
| 429 |
+ "badExtension": {
|
|
| 430 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 431 |
+ Name: "failure", |
|
| 432 |
+ Namespace: "mallet", |
|
| 433 |
+ }, |
|
| 434 |
+ Rules: []authorizationapi.PolicyRule{
|
|
| 435 |
+ {
|
|
| 436 |
+ Verbs: util.NewStringSet("watch", "list", "get"),
|
|
| 437 |
+ Resources: util.NewStringSet("buildConfigs"),
|
|
| 438 |
+ AttributeRestrictions: runtime.EmbeddedObject{&authorizationapi.Role{}},
|
|
| 439 |
+ }, |
|
| 440 |
+ {
|
|
| 441 |
+ Verbs: util.NewStringSet("update"),
|
|
| 442 |
+ Resources: util.NewStringSet("buildConfigs"),
|
|
| 443 |
+ }, |
|
| 444 |
+ }, |
|
| 445 |
+ }, |
|
| 446 |
+ }, |
|
| 447 |
+ }} |
|
| 448 |
+} |
|
| 449 |
+func newInvalidExtensionBindings() []authorizationapi.PolicyBinding {
|
|
| 450 |
+ return []authorizationapi.PolicyBinding{
|
|
| 451 |
+ {
|
|
| 452 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 453 |
+ Name: "mallet", |
|
| 454 |
+ Namespace: "mallet", |
|
| 455 |
+ }, |
|
| 456 |
+ RoleBindings: map[string]authorizationapi.RoleBinding{
|
|
| 457 |
+ "borked": {
|
|
| 458 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 459 |
+ Name: "borked", |
|
| 460 |
+ Namespace: "mallet", |
|
| 461 |
+ }, |
|
| 462 |
+ RoleRef: kapi.ObjectReference{
|
|
| 463 |
+ Name: "badExtension", |
|
| 464 |
+ Namespace: "mallet", |
|
| 465 |
+ }, |
|
| 466 |
+ Users: util.NewStringSet("Brad"),
|
|
| 467 |
+ }, |
|
| 468 |
+ }, |
|
| 469 |
+ }, |
|
| 470 |
+ } |
|
| 471 |
+} |
|
| 421 | 472 |
|
| 422 | 473 |
func GetBootstrapPolicy(masterNamespace string) *authorizationapi.Policy {
|
| 423 | 474 |
policy := &authorizationapi.Policy{
|
| 424 | 475 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package authorizer |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "errors" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "net/http" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/openshift/origin/pkg/api/latest" |
|
| 9 |
+ authorizationapi "github.com/openshift/origin/pkg/authorization/api" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func IsPersonalAccessReview(a AuthorizationAttributes) (bool, error) {
|
|
| 13 |
+ req, ok := a.GetRequestAttributes().(*http.Request) |
|
| 14 |
+ if !ok {
|
|
| 15 |
+ return false, errors.New("expected request, but did not get one")
|
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ // TODO once we're integrated with the api installer, we should have direct access to the deserialized content |
|
| 19 |
+ // for now, this only happens on subjectaccessreviews with a personal check, pay the double retrieve and decode cost |
|
| 20 |
+ body, err := ioutil.ReadAll(req.Body) |
|
| 21 |
+ if err != nil {
|
|
| 22 |
+ return false, err |
|
| 23 |
+ } |
|
| 24 |
+ req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) |
|
| 25 |
+ |
|
| 26 |
+ subjectAccessReview := &authorizationapi.SubjectAccessReview{}
|
|
| 27 |
+ if err := latest.Codec.DecodeInto(body, subjectAccessReview); err != nil {
|
|
| 28 |
+ return false, err |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ if (len(subjectAccessReview.User) == 0) && (len(subjectAccessReview.Groups) == 0) {
|
|
| 32 |
+ return true, nil |
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ return false, nil |
|
| 36 |
+} |
| ... | ... |
@@ -2,6 +2,7 @@ package bootstrappolicy |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
| 5 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 5 | 6 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
| 6 | 7 |
|
| 7 | 8 |
authorizationapi "github.com/openshift/origin/pkg/authorization/api" |
| ... | ... |
@@ -102,6 +103,7 @@ func GetBootstrapMasterRoles(masterNamespace string) []authorizationapi.Role {
|
| 102 | 102 |
Rules: []authorizationapi.PolicyRule{
|
| 103 | 103 |
{Verbs: util.NewStringSet("get"), Resources: util.NewStringSet("users"), ResourceNames: util.NewStringSet("~")},
|
| 104 | 104 |
{Verbs: util.NewStringSet("list"), Resources: util.NewStringSet("projects")},
|
| 105 |
+ {Verbs: util.NewStringSet("create"), Resources: util.NewStringSet("subjectaccessreviews"), AttributeRestrictions: runtime.EmbeddedObject{&authorizationapi.IsPersonalSubjectAccessReview{}}},
|
|
| 105 | 106 |
}, |
| 106 | 107 |
}, |
| 107 | 108 |
{
|
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
kapierror "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
| 11 | 11 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" |
| 12 | 12 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
| 13 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 13 | 14 |
|
| 14 | 15 |
authorizationapi "github.com/openshift/origin/pkg/authorization/api" |
| 15 | 16 |
"github.com/openshift/origin/pkg/client" |
| ... | ... |
@@ -106,3 +107,56 @@ func TestOverwritePolicyCommand(t *testing.T) {
|
| 106 | 106 |
t.Errorf("unexpected error: %v", err)
|
| 107 | 107 |
} |
| 108 | 108 |
} |
| 109 |
+ |
|
| 110 |
+func TestSelfSubjectAccessReviews(t *testing.T) {
|
|
| 111 |
+ _, clusterAdminKubeConfig, err := testutil.StartTestMaster() |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) |
|
| 117 |
+ if err != nil {
|
|
| 118 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ valerieClientConfig := *clusterAdminClientConfig |
|
| 122 |
+ valerieClientConfig.Username = "" |
|
| 123 |
+ valerieClientConfig.Password = "" |
|
| 124 |
+ valerieClientConfig.BearerToken = "" |
|
| 125 |
+ valerieClientConfig.CertFile = "" |
|
| 126 |
+ valerieClientConfig.KeyFile = "" |
|
| 127 |
+ valerieClientConfig.CertData = nil |
|
| 128 |
+ valerieClientConfig.KeyData = nil |
|
| 129 |
+ |
|
| 130 |
+ accessToken, err := tokencmd.RequestToken(&valerieClientConfig, nil, "valerie", "security!") |
|
| 131 |
+ if err != nil {
|
|
| 132 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 133 |
+ } |
|
| 134 |
+ |
|
| 135 |
+ valerieClientConfig.BearerToken = accessToken |
|
| 136 |
+ valerieOpenshiftClient, err := client.New(&valerieClientConfig) |
|
| 137 |
+ if err != nil {
|
|
| 138 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ // can I get a subjectaccessreview on myself even if I have no rights to do it generally |
|
| 142 |
+ askCanICreatePolicyBindings := &authorizationapi.SubjectAccessReview{Verb: "create", Resource: "policybindings"}
|
|
| 143 |
+ subjectAccessReviewTest{
|
|
| 144 |
+ clientInterface: valerieOpenshiftClient.SubjectAccessReviews("openshift"),
|
|
| 145 |
+ review: askCanICreatePolicyBindings, |
|
| 146 |
+ response: authorizationapi.SubjectAccessReviewResponse{
|
|
| 147 |
+ Allowed: false, |
|
| 148 |
+ Reason: "denied by default", |
|
| 149 |
+ Namespace: "openshift", |
|
| 150 |
+ }, |
|
| 151 |
+ }.run(t) |
|
| 152 |
+ |
|
| 153 |
+ // I shouldn't be allowed to ask whether someone else can perform an action |
|
| 154 |
+ askCanClusterAdminsCreateProject := &authorizationapi.SubjectAccessReview{Groups: util.NewStringSet("system:cluster-admins"), Verb: "create", Resource: "projects"}
|
|
| 155 |
+ subjectAccessReviewTest{
|
|
| 156 |
+ clientInterface: valerieOpenshiftClient.SubjectAccessReviews("openshift"),
|
|
| 157 |
+ review: askCanClusterAdminsCreateProject, |
|
| 158 |
+ err: "Forbidden", |
|
| 159 |
+ }.run(t) |
|
| 160 |
+ |
|
| 161 |
+} |