mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Add non-resource and API group support to ABAC authorizer, version ABAC policy rules
This commit is contained in:
		@@ -57,16 +57,18 @@ The following implementations are available, and are selected by flag:
 | 
			
		||||
 | 
			
		||||
### Request Attributes
 | 
			
		||||
 | 
			
		||||
A request has 5 attributes that can be considered for authorization:
 | 
			
		||||
A request has the following attributes that can be considered for authorization:
 | 
			
		||||
  - user (the user-string which a user was authenticated as).
 | 
			
		||||
  - group (the list of group names the authenticated user is a member of).
 | 
			
		||||
  - whether the request is readonly (GETs are readonly).
 | 
			
		||||
  - what resource is being accessed.
 | 
			
		||||
    - applies only to the API endpoints, such as
 | 
			
		||||
        `/api/v1/namespaces/default/pods`.  For miscellaneous endpoints, like `/version`, the
 | 
			
		||||
        resource is the empty string.
 | 
			
		||||
  - the namespace of the object being access, or the empty string if the
 | 
			
		||||
        endpoint does not support namespaced objects.
 | 
			
		||||
  - whether the request is for an API resource.
 | 
			
		||||
  - the request path.
 | 
			
		||||
    - allows authorizing access to miscellaneous endpoints like `/api` or `/healthz` (see [kubectl](#kubectl)).
 | 
			
		||||
  - the request verb.
 | 
			
		||||
    - API verbs like `get`, `list`, `create`, `update`, and `watch` are used for API requests
 | 
			
		||||
    - HTTP verbs like `get`, `post`, and `put` are used for non-API requests
 | 
			
		||||
  - what resource is being accessed (for API requests only)
 | 
			
		||||
  - the namespace of the object being accessed (for namespaced API requests only)
 | 
			
		||||
  - the API group being accessed (for API requests only)
 | 
			
		||||
 | 
			
		||||
We anticipate adding more attributes to allow finer grained access control and
 | 
			
		||||
to assist in policy management.
 | 
			
		||||
@@ -79,18 +81,29 @@ The file format is [one JSON object per line](http://jsonlines.org/).  There sho
 | 
			
		||||
one map per line.
 | 
			
		||||
 | 
			
		||||
Each line is a "policy object".  A policy object is a map with the following properties:
 | 
			
		||||
  - `user`, type string; the user-string from `--token-auth-file`. If you specify `user`, it must match the username of the authenticated user.
 | 
			
		||||
  - `group`, type string; if you specify `group`, it must match one of the groups of the authenticated user.
 | 
			
		||||
  - `readonly`, type boolean, when true, means that the policy only applies to GET
 | 
			
		||||
      operations.
 | 
			
		||||
  - `resource`, type string; a resource from an URL, such as `pods`.
 | 
			
		||||
  - `namespace`, type string; a namespace string.
 | 
			
		||||
  - Versioning properties:
 | 
			
		||||
    - `apiVersion`, type string; valid values are "abac.authorization.kubernetes.io/v1beta1". Allows versioning and conversion of the policy format.
 | 
			
		||||
    - `kind`, type string: valid values are "Policy". Allows versioning and conversion of the policy format.
 | 
			
		||||
 | 
			
		||||
  - `spec` property set to a map with the following properties:
 | 
			
		||||
    - Subject-matching properties:
 | 
			
		||||
      - `user`, type string; the user-string from `--token-auth-file`. If you specify `user`, it must match the username of the authenticated user. `*` matches all requests.
 | 
			
		||||
      - `group`, type string; if you specify `group`, it must match one of the groups of the authenticated user. `*` matches all requests.
 | 
			
		||||
 | 
			
		||||
    - `readonly`, type boolean, when true, means that the policy only applies to get, list, and watch operations.
 | 
			
		||||
 | 
			
		||||
    - Resource-matching properties:
 | 
			
		||||
      - `apiGroup`, type string; an API group, such as `extensions`. `*` matches all API groups.
 | 
			
		||||
      - `namespace`, type string; a namespace string. `*` matches all resource requests.
 | 
			
		||||
      - `resource`, type string; a resource, such as `pods`. `*` matches all resource requests.
 | 
			
		||||
 | 
			
		||||
    - Non-resource-matching properties:
 | 
			
		||||
    - `nonResourcePath`, type string; matches the non-resource request paths (like `/version` and `/apis`). `*` matches all non-resource requests. `/foo/*` matches `/foo/` and all of its subpaths.
 | 
			
		||||
 | 
			
		||||
An unset property is the same as a property set to the zero value for its type (e.g. empty string, 0, false).
 | 
			
		||||
However, unset should be preferred for readability.
 | 
			
		||||
 | 
			
		||||
In the future, policies may be expressed in a JSON format, and managed via a REST
 | 
			
		||||
interface.
 | 
			
		||||
In the future, policies may be expressed in a JSON format, and managed via a REST interface.
 | 
			
		||||
 | 
			
		||||
### Authorization Algorithm
 | 
			
		||||
 | 
			
		||||
@@ -99,21 +112,35 @@ A request has attributes which correspond to the properties of a policy object.
 | 
			
		||||
When a request is received, the attributes are determined.  Unknown attributes
 | 
			
		||||
are set to the zero value of its type (e.g. empty string, 0, false).
 | 
			
		||||
 | 
			
		||||
An unset property will match any value of the corresponding
 | 
			
		||||
attribute.  An unset attribute will match any value of the corresponding property.
 | 
			
		||||
A property set to "*" will match any value of the corresponding attribute.
 | 
			
		||||
 | 
			
		||||
The tuple of attributes is checked for a match against every policy in the policy file.
 | 
			
		||||
If at least one line matches the request attributes, then the request is authorized (but may fail later validation).
 | 
			
		||||
 | 
			
		||||
To permit any user to do something, write a policy with the user property unset.
 | 
			
		||||
To permit an action Policy with an unset namespace applies regardless of namespace.
 | 
			
		||||
To permit any user to do something, write a policy with the user property set to "*".
 | 
			
		||||
To permit a user to do anything, write a policy with the apiGroup, namespace, resource, and nonResourcePath properties set to "*".
 | 
			
		||||
 | 
			
		||||
### Kubectl
 | 
			
		||||
 | 
			
		||||
Kubectl uses the `/api` and `/apis` endpoints of api-server to negotiate client/server versions. To validate objects sent to the API by create/update operations, kubectl queries certain swagger resources. For API version `v1` those would be `/swaggerapi/api/v1` & `/swaggerapi/experimental/v1`.
 | 
			
		||||
 | 
			
		||||
When using ABAC authorization, those special resources have to be explicitly exposed via the `nonResourcePath` property in a policy (see [examples](#examples) below):
 | 
			
		||||
 | 
			
		||||
* `/api`, `/api/*`, `/apis`, and `/apis/*` for API version negotiation.
 | 
			
		||||
* `/version` for retrieving the server version via `kubectl version`.
 | 
			
		||||
* `/swaggerapi/*` for create/update operations.
 | 
			
		||||
 | 
			
		||||
To inspect the HTTP calls involved in a specific kubectl operation you can turn up the verbosity:
 | 
			
		||||
 | 
			
		||||
    kubectl --v=8 version
 | 
			
		||||
 | 
			
		||||
### Examples
 | 
			
		||||
 | 
			
		||||
 1. Alice can do anything: `{"user":"alice"}`
 | 
			
		||||
 2. Kubelet can read any pods: `{"user":"kubelet", "resource": "pods", "readonly": true}`
 | 
			
		||||
 3. Kubelet can read and write events: `{"user":"kubelet", "resource": "events"}`
 | 
			
		||||
 4. Bob can just read pods in namespace "projectCaribou": `{"user":"bob", "resource": "pods", "readonly": true, "namespace": "projectCaribou"}`
 | 
			
		||||
 1. Alice can do anything to all resources:                  `{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "alice", "namespace": "*", "resource": "*", "apiGroup": "*"}}`
 | 
			
		||||
 2. Kubelet can read any pods:                               `{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "pods", "readonly": true}}`
 | 
			
		||||
 3. Kubelet can read and write events:                       `{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "kubelet", "namespace": "*", "resource": "events"}}`
 | 
			
		||||
 4. Bob can just read pods in namespace "projectCaribou":    `{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}`
 | 
			
		||||
 5. Anyone can make read-only requests to all non-API paths: `{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "*", "readonly": true, "nonResourcePath": "*"}}`
 | 
			
		||||
 | 
			
		||||
[Complete file example](http://releases.k8s.io/HEAD/pkg/auth/authorizer/abac/example_policy_file.jsonl)
 | 
			
		||||
 | 
			
		||||
@@ -134,7 +161,7 @@ system:serviceaccount:<namespace>:default
 | 
			
		||||
For example, if you wanted to grant the default service account in the kube-system full privilege to the API, you would add this line to your policy file:
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{"user":"system:serviceaccount:kube-system:default"}
 | 
			
		||||
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","user":"system:serviceaccount:kube-system:default","namespace":"*","resource":"*","apiGroup":"*"}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The apiserver will need to be restarted to pickup the new policy lines.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								pkg/apis/abac/latest/latest.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								pkg/apis/abac/latest/latest.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2014 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 latest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac/v1beta1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Codec is the default codec for serializing input that should use the latest supported version.
 | 
			
		||||
var Codec = v1beta1.Codec
 | 
			
		||||
							
								
								
									
										37
									
								
								pkg/apis/abac/register.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								pkg/apis/abac/register.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/unversioned"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Group is the API group for abac
 | 
			
		||||
const Group = "abac.authorization.kubernetes.io"
 | 
			
		||||
 | 
			
		||||
// Scheme is the default instance of runtime.Scheme to which types in the abac API group are registered.
 | 
			
		||||
var Scheme = runtime.NewScheme()
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	Scheme.AddInternalGroupVersion(unversioned.GroupVersion{Group: Group, Version: ""})
 | 
			
		||||
	Scheme.AddKnownTypes(unversioned.GroupVersion{Group: Group, Version: ""},
 | 
			
		||||
		&Policy{},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*Policy) IsAnAPIObject() {}
 | 
			
		||||
							
								
								
									
										70
									
								
								pkg/apis/abac/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								pkg/apis/abac/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 api
 | 
			
		||||
 | 
			
		||||
import "k8s.io/kubernetes/pkg/api/unversioned"
 | 
			
		||||
 | 
			
		||||
// Policy contains a single ABAC policy rule
 | 
			
		||||
type Policy struct {
 | 
			
		||||
	unversioned.TypeMeta
 | 
			
		||||
 | 
			
		||||
	// Spec describes the policy rule
 | 
			
		||||
	Spec PolicySpec
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PolicySpec contains the attributes for a policy rule
 | 
			
		||||
type PolicySpec struct {
 | 
			
		||||
 | 
			
		||||
	// User is the username this rule applies to.
 | 
			
		||||
	// Either user or group is required to match the request.
 | 
			
		||||
	// "*" matches all users.
 | 
			
		||||
	User string
 | 
			
		||||
 | 
			
		||||
	// Group is the group this rule applies to.
 | 
			
		||||
	// Either user or group is required to match the request.
 | 
			
		||||
	// "*" matches all groups.
 | 
			
		||||
	Group string
 | 
			
		||||
 | 
			
		||||
	// Readonly matches readonly requests when true, and all requests when false
 | 
			
		||||
	Readonly bool
 | 
			
		||||
 | 
			
		||||
	// APIGroup is the name of an API group. APIGroup, Resource, and Namespace are required to match resource requests.
 | 
			
		||||
	// "*" matches all API groups
 | 
			
		||||
	APIGroup string
 | 
			
		||||
 | 
			
		||||
	// Resource is the name of a resource. APIGroup, Resource, and Namespace are required to match resource requests.
 | 
			
		||||
	// "*" matches all resources
 | 
			
		||||
	Resource string
 | 
			
		||||
 | 
			
		||||
	// Namespace is the name of a namespace. APIGroup, Resource, and Namespace are required to match resource requests.
 | 
			
		||||
	// "*" matches all namespaces (including unnamespaced requests)
 | 
			
		||||
	Namespace string
 | 
			
		||||
 | 
			
		||||
	// NonResourcePath matches non-resource request paths.
 | 
			
		||||
	// "*" matches all paths
 | 
			
		||||
	// "/foo/*" matches all subpaths of foo
 | 
			
		||||
	NonResourcePath string
 | 
			
		||||
 | 
			
		||||
	// TODO: "expires" string in RFC3339 format.
 | 
			
		||||
 | 
			
		||||
	// TODO: want a way to allow some users to restart containers of a pod but
 | 
			
		||||
	// not delete or modify it.
 | 
			
		||||
 | 
			
		||||
	// TODO: want a way to allow a controller to create a pod based only on a
 | 
			
		||||
	// certain podTemplates.
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								pkg/apis/abac/v0/conversion.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								pkg/apis/abac/v0/conversion.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 v0
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/conversion"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	api.Scheme.AddConversionFuncs(
 | 
			
		||||
		func(in *Policy, out *api.Policy, s conversion.Scope) error {
 | 
			
		||||
			// Begin by copying all fields
 | 
			
		||||
			out.Spec.User = in.User
 | 
			
		||||
			out.Spec.Group = in.Group
 | 
			
		||||
			out.Spec.Namespace = in.Namespace
 | 
			
		||||
			out.Spec.Resource = in.Resource
 | 
			
		||||
			out.Spec.Readonly = in.Readonly
 | 
			
		||||
 | 
			
		||||
			// In v0, unspecified user and group matches all subjects
 | 
			
		||||
			if len(in.User) == 0 && len(in.Group) == 0 {
 | 
			
		||||
				out.Spec.User = "*"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// In v0, leaving namespace empty matches all namespaces
 | 
			
		||||
			if len(in.Namespace) == 0 {
 | 
			
		||||
				out.Spec.Namespace = "*"
 | 
			
		||||
			}
 | 
			
		||||
			// In v0, leaving resource empty matches all resources
 | 
			
		||||
			if len(in.Resource) == 0 {
 | 
			
		||||
				out.Spec.Resource = "*"
 | 
			
		||||
			}
 | 
			
		||||
			// Any rule in v0 should match all API groups
 | 
			
		||||
			out.Spec.APIGroup = "*"
 | 
			
		||||
 | 
			
		||||
			// In v0, leaving namespace and resource blank allows non-resource paths
 | 
			
		||||
			if len(in.Namespace) == 0 && len(in.Resource) == 0 {
 | 
			
		||||
				out.Spec.NonResourcePath = "*"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								pkg/apis/abac/v0/conversion_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								pkg/apis/abac/v0/conversion_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 v0_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac/v0"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestConversion(t *testing.T) {
 | 
			
		||||
	testcases := map[string]struct {
 | 
			
		||||
		old      *v0.Policy
 | 
			
		||||
		expected *api.Policy
 | 
			
		||||
	}{
 | 
			
		||||
		// a completely empty policy rule allows everything to all users
 | 
			
		||||
		"empty": {
 | 
			
		||||
			old:      &v0.Policy{},
 | 
			
		||||
			expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// specifying a user is preserved
 | 
			
		||||
		"user": {
 | 
			
		||||
			old:      &v0.Policy{User: "bob"},
 | 
			
		||||
			expected: &api.Policy{Spec: api.PolicySpec{User: "bob", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// specifying a group is preserved (and no longer matches all users)
 | 
			
		||||
		"group": {
 | 
			
		||||
			old:      &v0.Policy{Group: "mygroup"},
 | 
			
		||||
			expected: &api.Policy{Spec: api.PolicySpec{Group: "mygroup", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// specifying a namespace removes the * match on non-resource path
 | 
			
		||||
		"namespace": {
 | 
			
		||||
			old:      &v0.Policy{Namespace: "myns"},
 | 
			
		||||
			expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "*", APIGroup: "*"}},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// specifying a resource removes the * match on non-resource path
 | 
			
		||||
		"resource": {
 | 
			
		||||
			old:      &v0.Policy{Resource: "myresource"},
 | 
			
		||||
			expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "", Namespace: "*", Resource: "myresource", APIGroup: "*"}},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// specifying a namespace+resource removes the * match on non-resource path
 | 
			
		||||
		"namespace+resource": {
 | 
			
		||||
			old:      &v0.Policy{Namespace: "myns", Resource: "myresource"},
 | 
			
		||||
			expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "myresource", APIGroup: "*"}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for k, tc := range testcases {
 | 
			
		||||
		internal := &api.Policy{}
 | 
			
		||||
		if err := api.Scheme.Convert(tc.old, internal); err != nil {
 | 
			
		||||
			t.Errorf("%s: unexpected error: %v", k, err)
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(internal, tc.expected) {
 | 
			
		||||
			t.Errorf("%s: expected\n\t%#v, got \n\t%#v", k, tc.expected, internal)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								pkg/apis/abac/v0/register.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								pkg/apis/abac/v0/register.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 v0
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/unversioned"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GroupVersion is the API group and version for abac v0
 | 
			
		||||
var GroupVersion = unversioned.GroupVersion{Group: api.Group, Version: "v0"}
 | 
			
		||||
 | 
			
		||||
// Codec encodes internal objects to the v0 version for the abac group
 | 
			
		||||
var Codec = runtime.CodecFor(api.Scheme, GroupVersion.String())
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	api.Scheme.AddKnownTypes(GroupVersion,
 | 
			
		||||
		&Policy{},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*Policy) IsAnAPIObject() {}
 | 
			
		||||
							
								
								
									
										45
									
								
								pkg/apis/abac/v0/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								pkg/apis/abac/v0/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 v0
 | 
			
		||||
 | 
			
		||||
import "k8s.io/kubernetes/pkg/api/unversioned"
 | 
			
		||||
 | 
			
		||||
// Policy contains a single ABAC policy rule
 | 
			
		||||
type Policy struct {
 | 
			
		||||
	unversioned.TypeMeta `json:",inline"`
 | 
			
		||||
 | 
			
		||||
	// User is the username this rule applies to.
 | 
			
		||||
	// Either user or group is required to match the request.
 | 
			
		||||
	// "*" matches all users.
 | 
			
		||||
	User string `json:"user,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Group is the group this rule applies to.
 | 
			
		||||
	// Either user or group is required to match the request.
 | 
			
		||||
	// "*" matches all groups.
 | 
			
		||||
	Group string `json:"group,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Readonly matches readonly requests when true, and all requests when false
 | 
			
		||||
	Readonly bool `json:"readonly,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Resource is the name of a resource
 | 
			
		||||
	// "*" matches all resources
 | 
			
		||||
	Resource string `json:"resource,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Namespace is the name of a namespace
 | 
			
		||||
	// "*" matches all namespaces (including unnamespaced requests)
 | 
			
		||||
	Namespace string `json:"namespace,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								pkg/apis/abac/v1beta1/register.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								pkg/apis/abac/v1beta1/register.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 v1beta1
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/unversioned"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GroupVersion is the API group and version for abac v1beta1
 | 
			
		||||
var GroupVersion = unversioned.GroupVersion{Group: api.Group, Version: "v1beta1"}
 | 
			
		||||
 | 
			
		||||
// Codec encodes internal objects to the v1beta1 version for the abac group
 | 
			
		||||
var Codec = runtime.CodecFor(api.Scheme, GroupVersion.String())
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	api.Scheme.AddKnownTypes(GroupVersion,
 | 
			
		||||
		&Policy{},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*Policy) IsAnAPIObject() {}
 | 
			
		||||
							
								
								
									
										60
									
								
								pkg/apis/abac/v1beta1/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								pkg/apis/abac/v1beta1/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 v1beta1
 | 
			
		||||
 | 
			
		||||
import "k8s.io/kubernetes/pkg/api/unversioned"
 | 
			
		||||
 | 
			
		||||
// Policy contains a single ABAC policy rule
 | 
			
		||||
type Policy struct {
 | 
			
		||||
	unversioned.TypeMeta `json:",inline"`
 | 
			
		||||
 | 
			
		||||
	// Spec describes the policy rule
 | 
			
		||||
	Spec PolicySpec `json:"spec"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PolicySpec contains the attributes for a policy rule
 | 
			
		||||
type PolicySpec struct {
 | 
			
		||||
	// User is the username this rule applies to.
 | 
			
		||||
	// Either user or group is required to match the request.
 | 
			
		||||
	// "*" matches all users.
 | 
			
		||||
	User string `json:"user,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Group is the group this rule applies to.
 | 
			
		||||
	// Either user or group is required to match the request.
 | 
			
		||||
	// "*" matches all groups.
 | 
			
		||||
	Group string `json:"group,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Readonly matches readonly requests when true, and all requests when false
 | 
			
		||||
	Readonly bool `json:"readonly,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// APIGroup is the name of an API group. APIGroup, Resource, and Namespace are required to match resource requests.
 | 
			
		||||
	// "*" matches all API groups
 | 
			
		||||
	APIGroup string `json:"apiGroup,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Resource is the name of a resource. APIGroup, Resource, and Namespace are required to match resource requests.
 | 
			
		||||
	// "*" matches all resources
 | 
			
		||||
	Resource string `json:"resource,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// Namespace is the name of a namespace. APIGroup, Resource, and Namespace are required to match resource requests.
 | 
			
		||||
	// "*" matches all namespaces (including unnamespaced requests)
 | 
			
		||||
	Namespace string `json:"namespace,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// NonResourcePath matches non-resource request paths.
 | 
			
		||||
	// "*" matches all paths
 | 
			
		||||
	// "/foo/*" matches all subpaths of foo
 | 
			
		||||
	NonResourcePath string `json:"nonResourcePath,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
@@ -362,19 +362,24 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	apiRequestInfo, _ := r.requestInfoResolver.GetRequestInfo(req)
 | 
			
		||||
	requestInfo, _ := r.requestInfoResolver.GetRequestInfo(req)
 | 
			
		||||
 | 
			
		||||
	attribs.APIGroup = apiRequestInfo.APIGroup
 | 
			
		||||
	attribs.Verb = apiRequestInfo.Verb
 | 
			
		||||
	// Start with common attributes that apply to resource and non-resource requests
 | 
			
		||||
	attribs.ResourceRequest = requestInfo.IsResourceRequest
 | 
			
		||||
	attribs.Path = requestInfo.Path
 | 
			
		||||
	attribs.Verb = requestInfo.Verb
 | 
			
		||||
 | 
			
		||||
	// If the request was for a resource in an API group, include that info
 | 
			
		||||
	attribs.APIGroup = requestInfo.APIGroup
 | 
			
		||||
 | 
			
		||||
	// If a path follows the conventions of the REST object store, then
 | 
			
		||||
	// we can extract the resource.  Otherwise, not.
 | 
			
		||||
	attribs.Resource = apiRequestInfo.Resource
 | 
			
		||||
	attribs.Resource = requestInfo.Resource
 | 
			
		||||
 | 
			
		||||
	// If the request specifies a namespace, then the namespace is filled in.
 | 
			
		||||
	// Assumes there is no empty string namespace.  Unspecified results
 | 
			
		||||
	// in empty (does not understand defaulting rules.)
 | 
			
		||||
	attribs.Namespace = apiRequestInfo.Namespace
 | 
			
		||||
	attribs.Namespace = requestInfo.Namespace
 | 
			
		||||
 | 
			
		||||
	return &attribs
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,8 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/errors"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/testapi"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/auth/authorizer"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/sets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type fakeRL bool
 | 
			
		||||
@@ -218,6 +220,83 @@ func TestTimeout(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetAttribs(t *testing.T) {
 | 
			
		||||
	r := &requestAttributeGetter{api.NewRequestContextMapper(), &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}}
 | 
			
		||||
 | 
			
		||||
	testcases := map[string]struct {
 | 
			
		||||
		Verb               string
 | 
			
		||||
		Path               string
 | 
			
		||||
		ExpectedAttributes *authorizer.AttributesRecord
 | 
			
		||||
	}{
 | 
			
		||||
		"non-resource root": {
 | 
			
		||||
			Verb: "POST",
 | 
			
		||||
			Path: "/",
 | 
			
		||||
			ExpectedAttributes: &authorizer.AttributesRecord{
 | 
			
		||||
				Verb: "post",
 | 
			
		||||
				Path: "/",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"non-resource api prefix": {
 | 
			
		||||
			Verb: "GET",
 | 
			
		||||
			Path: "/api/",
 | 
			
		||||
			ExpectedAttributes: &authorizer.AttributesRecord{
 | 
			
		||||
				Verb: "get",
 | 
			
		||||
				Path: "/api/",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"non-resource group api prefix": {
 | 
			
		||||
			Verb: "GET",
 | 
			
		||||
			Path: "/apis/extensions/",
 | 
			
		||||
			ExpectedAttributes: &authorizer.AttributesRecord{
 | 
			
		||||
				Verb: "get",
 | 
			
		||||
				Path: "/apis/extensions/",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"resource": {
 | 
			
		||||
			Verb: "POST",
 | 
			
		||||
			Path: "/api/v1/nodes/mynode",
 | 
			
		||||
			ExpectedAttributes: &authorizer.AttributesRecord{
 | 
			
		||||
				Verb:            "create",
 | 
			
		||||
				Path:            "/api/v1/nodes/mynode",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
				Resource:        "nodes",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"namespaced resource": {
 | 
			
		||||
			Verb: "PUT",
 | 
			
		||||
			Path: "/api/v1/namespaces/myns/pods/mypod",
 | 
			
		||||
			ExpectedAttributes: &authorizer.AttributesRecord{
 | 
			
		||||
				Verb:            "update",
 | 
			
		||||
				Path:            "/api/v1/namespaces/myns/pods/mypod",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
				Namespace:       "myns",
 | 
			
		||||
				Resource:        "pods",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"API group resource": {
 | 
			
		||||
			Verb: "GET",
 | 
			
		||||
			Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
 | 
			
		||||
			ExpectedAttributes: &authorizer.AttributesRecord{
 | 
			
		||||
				Verb:            "list",
 | 
			
		||||
				Path:            "/apis/extensions/v1beta1/namespaces/myns/jobs",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
				APIGroup:        "extensions",
 | 
			
		||||
				Namespace:       "myns",
 | 
			
		||||
				Resource:        "jobs",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for k, tc := range testcases {
 | 
			
		||||
		req, _ := http.NewRequest(tc.Verb, tc.Path, nil)
 | 
			
		||||
		attribs := r.GetAttribs(req)
 | 
			
		||||
		if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
 | 
			
		||||
			t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetAPIRequestInfo(t *testing.T) {
 | 
			
		||||
	successCases := []struct {
 | 
			
		||||
		method              string
 | 
			
		||||
 
 | 
			
		||||
@@ -21,50 +21,35 @@ package abac
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac/latest"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac/v0"
 | 
			
		||||
	_ "k8s.io/kubernetes/pkg/apis/abac/v1beta1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/auth/authorizer"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO: make this into a real API object.  Note that when that happens, it
 | 
			
		||||
// will get MetaData.  However, the Kind and Namespace in the struct below
 | 
			
		||||
// will be separate from the Kind and Namespace in the Metadata.  Obviously,
 | 
			
		||||
// meta.Kind will be something like policy, and policy.Kind has to be allowed
 | 
			
		||||
// to be different.  Less obviously, namespace needs to be different as well.
 | 
			
		||||
// This will allow wildcard matching strings to be used in the future for the
 | 
			
		||||
// body.Namespace, if we want to add that feature, without affecting the
 | 
			
		||||
// meta.Namespace.
 | 
			
		||||
type policy struct {
 | 
			
		||||
	User  string `json:"user,omitempty"`
 | 
			
		||||
	Group string `json:"group,omitempty"`
 | 
			
		||||
	// TODO: add support for robot accounts as well as human user accounts.
 | 
			
		||||
	// TODO: decide how to namespace user names when multiple authentication
 | 
			
		||||
	// providers are in use. Either add "Realm", or assume "user@example.com"
 | 
			
		||||
	// format.
 | 
			
		||||
 | 
			
		||||
	// TODO: Make the "cluster" Kinds be one API group (nodes, bindings,
 | 
			
		||||
	// events, endpoints).  The "user" Kinds are another (pods, services,
 | 
			
		||||
	// replicationControllers, operations) Make a "plugin", e.g. build
 | 
			
		||||
	// controller, be another group.  That way when we add a new object to a
 | 
			
		||||
	// the API, we don't have to add lots of policy?
 | 
			
		||||
 | 
			
		||||
	// TODO: make this a proper REST object with its own registry.
 | 
			
		||||
	Readonly  bool   `json:"readonly,omitempty"`
 | 
			
		||||
	Resource  string `json:"resource,omitempty"`
 | 
			
		||||
	Namespace string `json:"namespace,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// TODO: "expires" string in RFC3339 format.
 | 
			
		||||
 | 
			
		||||
	// TODO: want a way to allow some users to restart containers of a pod but
 | 
			
		||||
	// not delete or modify it.
 | 
			
		||||
 | 
			
		||||
	// TODO: want a way to allow a controller to create a pod based only on a
 | 
			
		||||
	// certain podTemplates.
 | 
			
		||||
type policyLoadError struct {
 | 
			
		||||
	path string
 | 
			
		||||
	line int
 | 
			
		||||
	data []byte
 | 
			
		||||
	err  error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type policyList []policy
 | 
			
		||||
func (p policyLoadError) Error() string {
 | 
			
		||||
	if p.line >= 0 {
 | 
			
		||||
		return fmt.Sprintf("error reading policy file %s, line %d: %s: %v", p.path, p.line, string(p.data), p.err)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("error reading policy file %s: %v", p.path, p.err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type policyList []*api.Policy
 | 
			
		||||
 | 
			
		||||
// TODO: Have policies be created via an API call and stored in REST storage.
 | 
			
		||||
func NewFromFile(path string) (policyList, error) {
 | 
			
		||||
@@ -79,62 +64,163 @@ func NewFromFile(path string) (policyList, error) {
 | 
			
		||||
	scanner := bufio.NewScanner(file)
 | 
			
		||||
	pl := make(policyList, 0)
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	unversionedLines := 0
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		var p policy
 | 
			
		||||
		i++
 | 
			
		||||
		p := &api.Policy{}
 | 
			
		||||
		b := scanner.Bytes()
 | 
			
		||||
		// TODO: skip comment lines.
 | 
			
		||||
		err = json.Unmarshal(b, &p)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// TODO: line number in errors.
 | 
			
		||||
			return nil, err
 | 
			
		||||
 | 
			
		||||
		// skip comment lines and blank lines
 | 
			
		||||
		trimmed := strings.TrimSpace(string(b))
 | 
			
		||||
		if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		version, kind, err := api.Scheme.DataVersionAndKind(b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, policyLoadError{path, i, b, err}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if version == "" && kind == "" {
 | 
			
		||||
			unversionedLines++
 | 
			
		||||
			// Migrate unversioned policy object
 | 
			
		||||
			oldPolicy := &v0.Policy{}
 | 
			
		||||
			if err := latest.Codec.DecodeInto(b, oldPolicy); err != nil {
 | 
			
		||||
				return nil, policyLoadError{path, i, b, err}
 | 
			
		||||
			}
 | 
			
		||||
			if err := api.Scheme.Convert(oldPolicy, p); err != nil {
 | 
			
		||||
				return nil, policyLoadError{path, i, b, err}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			decodedObj, err := latest.Codec.Decode(b)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, policyLoadError{path, i, b, err}
 | 
			
		||||
			}
 | 
			
		||||
			decodedPolicy, ok := decodedObj.(*api.Policy)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return nil, policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v", decodedObj)}
 | 
			
		||||
			}
 | 
			
		||||
			p = decodedPolicy
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pl = append(pl, p)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if unversionedLines > 0 {
 | 
			
		||||
		glog.Warningf(`Policy file %s contained unversioned rules. See docs/admin/authorization.md#abac-mode for ABAC file format details.`, path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := scanner.Err(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, policyLoadError{path, -1, nil, err}
 | 
			
		||||
	}
 | 
			
		||||
	return pl, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p policy) matches(a authorizer.Attributes) bool {
 | 
			
		||||
	if p.subjectMatches(a) {
 | 
			
		||||
		if p.Readonly == false || (p.Readonly == a.IsReadOnly()) {
 | 
			
		||||
			if p.Resource == "" || (p.Resource == a.GetResource()) {
 | 
			
		||||
				if p.Namespace == "" || (p.Namespace == a.GetNamespace()) {
 | 
			
		||||
func matches(p api.Policy, a authorizer.Attributes) bool {
 | 
			
		||||
	if subjectMatches(p, a) {
 | 
			
		||||
		if verbMatches(p, a) {
 | 
			
		||||
			// Resource and non-resource requests are mutually exclusive, at most one will match a policy
 | 
			
		||||
			if resourceMatches(p, a) {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
			if nonResourceMatches(p, a) {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p policy) subjectMatches(a authorizer.Attributes) bool {
 | 
			
		||||
	if p.User != "" {
 | 
			
		||||
		// Require user match
 | 
			
		||||
		if p.User != a.GetUserName() {
 | 
			
		||||
// subjectMatches returns true if specified user and group properties in the policy match the attributes
 | 
			
		||||
func subjectMatches(p api.Policy, a authorizer.Attributes) bool {
 | 
			
		||||
	matched := false
 | 
			
		||||
 | 
			
		||||
	// If the policy specified a user, ensure it matches
 | 
			
		||||
	if len(p.Spec.User) > 0 {
 | 
			
		||||
		if p.Spec.User == "*" {
 | 
			
		||||
			matched = true
 | 
			
		||||
		} else {
 | 
			
		||||
			matched = p.Spec.User == a.GetUserName()
 | 
			
		||||
			if !matched {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.Group != "" {
 | 
			
		||||
		// Require group match
 | 
			
		||||
	// If the policy specified a group, ensure it matches
 | 
			
		||||
	if len(p.Spec.Group) > 0 {
 | 
			
		||||
		if p.Spec.Group == "*" {
 | 
			
		||||
			matched = true
 | 
			
		||||
		} else {
 | 
			
		||||
			matched = false
 | 
			
		||||
			for _, group := range a.GetGroups() {
 | 
			
		||||
			if p.Group == group {
 | 
			
		||||
				if p.Spec.Group == group {
 | 
			
		||||
					matched = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !matched {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return matched
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func verbMatches(p api.Policy, a authorizer.Attributes) bool {
 | 
			
		||||
	// TODO: match on verb
 | 
			
		||||
 | 
			
		||||
	// All policies allow read only requests
 | 
			
		||||
	if a.IsReadOnly() {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Allow if policy is not readonly
 | 
			
		||||
	if !p.Spec.Readonly {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nonResourceMatches(p api.Policy, a authorizer.Attributes) bool {
 | 
			
		||||
	// A non-resource policy cannot match a resource request
 | 
			
		||||
	if !a.IsResourceRequest() {
 | 
			
		||||
		// Allow wildcard match
 | 
			
		||||
		if p.Spec.NonResourcePath == "*" {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		// Allow exact match
 | 
			
		||||
		if p.Spec.NonResourcePath == a.GetPath() {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		// Allow a trailing * subpath match
 | 
			
		||||
		if strings.HasSuffix(p.Spec.NonResourcePath, "*") && strings.HasPrefix(a.GetPath(), strings.TrimRight(p.Spec.NonResourcePath, "*")) {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resourceMatches(p api.Policy, a authorizer.Attributes) bool {
 | 
			
		||||
	// A resource policy cannot match a non-resource request
 | 
			
		||||
	if a.IsResourceRequest() {
 | 
			
		||||
		if p.Spec.Namespace == "*" || p.Spec.Namespace == a.GetNamespace() {
 | 
			
		||||
			if p.Spec.Resource == "*" || p.Spec.Resource == a.GetResource() {
 | 
			
		||||
				if p.Spec.APIGroup == "*" || p.Spec.APIGroup == a.GetAPIGroup() {
 | 
			
		||||
					return true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Authorizer implements authorizer.Authorize
 | 
			
		||||
func (pl policyList) Authorize(a authorizer.Attributes) error {
 | 
			
		||||
	for _, p := range pl {
 | 
			
		||||
		if p.matches(a) {
 | 
			
		||||
		if matches(*p, a) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,12 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac/v0"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/abac/v1beta1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/auth/authorizer"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/auth/user"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestEmptyFile(t *testing.T) {
 | 
			
		||||
@@ -56,7 +60,7 @@ func TestExampleFile(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNotAuthorized(t *testing.T) {
 | 
			
		||||
func TestAuthorizeV0(t *testing.T) {
 | 
			
		||||
	a, err := newWithContents(t, `{                    "readonly": true, "resource": "events"   }
 | 
			
		||||
{"user":"scheduler", "readonly": true, "resource": "pods"     }
 | 
			
		||||
{"user":"scheduler",                   "resource": "bindings" }
 | 
			
		||||
@@ -78,6 +82,102 @@ func TestNotAuthorized(t *testing.T) {
 | 
			
		||||
		Verb        string
 | 
			
		||||
		Resource    string
 | 
			
		||||
		NS          string
 | 
			
		||||
		APIGroup    string
 | 
			
		||||
		Path        string
 | 
			
		||||
		ExpectAllow bool
 | 
			
		||||
	}{
 | 
			
		||||
		// Scheduler can read pods
 | 
			
		||||
		{User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectAllow: true},
 | 
			
		||||
		{User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectAllow: true},
 | 
			
		||||
		// Scheduler cannot write pods
 | 
			
		||||
		{User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectAllow: false},
 | 
			
		||||
		// Scheduler can write bindings
 | 
			
		||||
		{User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectAllow: true},
 | 
			
		||||
		{User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectAllow: true},
 | 
			
		||||
 | 
			
		||||
		// Alice can read and write anything in the right namespace.
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectAllow: true},
 | 
			
		||||
		{User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
 | 
			
		||||
		{User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
 | 
			
		||||
		{User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectAllow: true},
 | 
			
		||||
		{User: uAlice, Verb: "update", Resource: "foo", NS: "projectCaribou", APIGroup: "bar", ExpectAllow: true},
 | 
			
		||||
		// .. but not the wrong namespace.
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
 | 
			
		||||
		// Chuck can read events, since anyone can.
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true},
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true},
 | 
			
		||||
		// Chuck can't do other things.
 | 
			
		||||
		{User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		// Chunk can't access things with no kind or namespace
 | 
			
		||||
		{User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tc := range testCases {
 | 
			
		||||
		attr := authorizer.AttributesRecord{
 | 
			
		||||
			User:      &tc.User,
 | 
			
		||||
			Verb:      tc.Verb,
 | 
			
		||||
			Resource:  tc.Resource,
 | 
			
		||||
			Namespace: tc.NS,
 | 
			
		||||
			APIGroup:  tc.APIGroup,
 | 
			
		||||
			Path:      tc.Path,
 | 
			
		||||
 | 
			
		||||
			ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0,
 | 
			
		||||
		}
 | 
			
		||||
		err := a.Authorize(attr)
 | 
			
		||||
		actualAllow := bool(err == nil)
 | 
			
		||||
		if tc.ExpectAllow != actualAllow {
 | 
			
		||||
			t.Logf("tc: %v -> attr %v", tc, attr)
 | 
			
		||||
			t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v",
 | 
			
		||||
				i, tc.ExpectAllow, actualAllow, tc)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAuthorizeV1beta1(t *testing.T) {
 | 
			
		||||
	a, err := newWithContents(t,
 | 
			
		||||
		`
 | 
			
		||||
		 # Comment line, after a blank line
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*",         "readonly": true,                                                        "nonResourcePath": "/api"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*",                                                                                  "nonResourcePath": "/custom"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*",                                                                                  "nonResourcePath": "/root/*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"noresource",                                                                         "nonResourcePath": "*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*",         "readonly": true, "resource": "events",   "namespace": "*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"scheduler", "readonly": true, "resource": "pods",     "namespace": "*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"scheduler",                   "resource": "bindings", "namespace": "*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"kubelet",   "readonly": true, "resource": "bindings", "namespace": "*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"kubelet",                     "resource": "events",   "namespace": "*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"alice",                       "resource": "*",        "namespace": "projectCaribou"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"bob",       "readonly": true, "resource": "*",        "namespace": "projectCaribou"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"debbie",                      "resource": "pods",     "namespace": "projectCaribou"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser",                "resource": "*",        "namespace": "projectAnyGroup",   "apiGroup": "*"}}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser",                "resource": "*",        "namespace": "projectEmptyGroup", "apiGroup": "" }}
 | 
			
		||||
		 {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser",                "resource": "*",        "namespace": "projectXGroup",     "apiGroup": "x"}}`)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unable to read policy file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1"}
 | 
			
		||||
	uAlice := user.DefaultInfo{Name: "alice", UID: "uid3"}
 | 
			
		||||
	uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5"}
 | 
			
		||||
	uDebbie := user.DefaultInfo{Name: "debbie", UID: "uid6"}
 | 
			
		||||
	uNoResource := user.DefaultInfo{Name: "noresource", UID: "uid7"}
 | 
			
		||||
	uAPIGroup := user.DefaultInfo{Name: "apigroupuser", UID: "uid8"}
 | 
			
		||||
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		User        user.DefaultInfo
 | 
			
		||||
		Verb        string
 | 
			
		||||
		Resource    string
 | 
			
		||||
		APIGroup    string
 | 
			
		||||
		NS          string
 | 
			
		||||
		Path        string
 | 
			
		||||
		ExpectAllow bool
 | 
			
		||||
	}{
 | 
			
		||||
		// Scheduler can read pods
 | 
			
		||||
@@ -102,6 +202,9 @@ func TestNotAuthorized(t *testing.T) {
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
 | 
			
		||||
		// Debbie can write to pods in the right namespace
 | 
			
		||||
		{User: uDebbie, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
 | 
			
		||||
 | 
			
		||||
		// Chuck can read events, since anyone can.
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true},
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true},
 | 
			
		||||
@@ -109,24 +212,49 @@ func TestNotAuthorized(t *testing.T) {
 | 
			
		||||
		{User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false},
 | 
			
		||||
		// Chunk can't access things with no kind or namespace
 | 
			
		||||
		// TODO: find a way to give someone access to miscellaneous endpoints, such as
 | 
			
		||||
		// /healthz, /version, etc.
 | 
			
		||||
		{User: uChuck, Verb: "get", Resource: "", NS: "", ExpectAllow: false},
 | 
			
		||||
		// Chuck can't access things with no resource or namespace
 | 
			
		||||
		{User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false},
 | 
			
		||||
		// but can access /api
 | 
			
		||||
		{User: uChuck, Verb: "get", Path: "/api", Resource: "", NS: "", ExpectAllow: true},
 | 
			
		||||
		// though he cannot write to it
 | 
			
		||||
		{User: uChuck, Verb: "create", Path: "/api", Resource: "", NS: "", ExpectAllow: false},
 | 
			
		||||
		// while he can write to /custom
 | 
			
		||||
		{User: uChuck, Verb: "update", Path: "/custom", Resource: "", NS: "", ExpectAllow: true},
 | 
			
		||||
		// he cannot get "/root"
 | 
			
		||||
		{User: uChuck, Verb: "get", Path: "/root", Resource: "", NS: "", ExpectAllow: false},
 | 
			
		||||
		// but can get any subpath
 | 
			
		||||
		{User: uChuck, Verb: "get", Path: "/root/", Resource: "", NS: "", ExpectAllow: true},
 | 
			
		||||
		{User: uChuck, Verb: "get", Path: "/root/test/1/2/3", Resource: "", NS: "", ExpectAllow: true},
 | 
			
		||||
 | 
			
		||||
		// the user "noresource" can get any non-resource request
 | 
			
		||||
		{User: uNoResource, Verb: "get", Path: "", Resource: "", NS: "", ExpectAllow: true},
 | 
			
		||||
		{User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: true},
 | 
			
		||||
		{User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "", NS: "", ExpectAllow: true},
 | 
			
		||||
		// but cannot get any request where IsResourceRequest() == true
 | 
			
		||||
		{User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "bar", ExpectAllow: false},
 | 
			
		||||
		{User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "foo", NS: "bar", ExpectAllow: false},
 | 
			
		||||
 | 
			
		||||
		// Test APIGroup matching
 | 
			
		||||
		{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectAnyGroup", ExpectAllow: true},
 | 
			
		||||
		{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectEmptyGroup", ExpectAllow: false},
 | 
			
		||||
		{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectXGroup", ExpectAllow: true},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tc := range testCases {
 | 
			
		||||
		attr := authorizer.AttributesRecord{
 | 
			
		||||
			User:            &tc.User,
 | 
			
		||||
			Verb:            tc.Verb,
 | 
			
		||||
			Resource:        tc.Resource,
 | 
			
		||||
			APIGroup:        tc.APIGroup,
 | 
			
		||||
			Namespace:       tc.NS,
 | 
			
		||||
			ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0,
 | 
			
		||||
			Path:            tc.Path,
 | 
			
		||||
		}
 | 
			
		||||
		t.Logf("tc: %v -> attr %v", tc, attr)
 | 
			
		||||
		// t.Logf("tc %2v: %v -> attr %v", i, tc, attr)
 | 
			
		||||
		err := a.Authorize(attr)
 | 
			
		||||
		actualAllow := bool(err == nil)
 | 
			
		||||
		if tc.ExpectAllow != actualAllow {
 | 
			
		||||
			t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v",
 | 
			
		||||
				i, tc.ExpectAllow, actualAllow, tc)
 | 
			
		||||
			t.Errorf("%d: Expected allowed=%v but actually allowed=%v, for case %+v & %+v",
 | 
			
		||||
				i, tc.ExpectAllow, actualAllow, tc, attr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -134,116 +262,316 @@ func TestNotAuthorized(t *testing.T) {
 | 
			
		||||
func TestSubjectMatches(t *testing.T) {
 | 
			
		||||
	testCases := map[string]struct {
 | 
			
		||||
		User        user.DefaultInfo
 | 
			
		||||
		PolicyUser  string
 | 
			
		||||
		PolicyGroup string
 | 
			
		||||
		Policy      runtime.Object
 | 
			
		||||
		ExpectMatch bool
 | 
			
		||||
	}{
 | 
			
		||||
		"empty policy matches unauthed user": {
 | 
			
		||||
		"v0 empty policy matches unauthed user": {
 | 
			
		||||
			User: user.DefaultInfo{},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
		"empty policy matches authed user": {
 | 
			
		||||
		"v0 empty policy matches authed user": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo"},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
		"empty policy matches authed user with groups": {
 | 
			
		||||
		"v0 empty policy matches authed user with groups": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b"}},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"user policy does not match unauthed user": {
 | 
			
		||||
		"v0 user policy does not match unauthed user": {
 | 
			
		||||
			User: user.DefaultInfo{},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"user policy does not match different user": {
 | 
			
		||||
		"v0 user policy does not match different user": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Bar"},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"user policy is case-sensitive": {
 | 
			
		||||
		"v0 user policy is case-sensitive": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "foo"},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"user policy does not match substring": {
 | 
			
		||||
		"v0 user policy does not match substring": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "FooBar"},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"user policy matches username": {
 | 
			
		||||
		"v0 user policy matches username": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo"},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"group policy does not match unauthed user": {
 | 
			
		||||
		"v0 group policy does not match unauthed user": {
 | 
			
		||||
			User: user.DefaultInfo{},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "Foo",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "Foo",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"group policy does not match user in different group": {
 | 
			
		||||
		"v0 group policy does not match user in different group": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B"}},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "A",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "A",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"group policy is case-sensitive": {
 | 
			
		||||
		"v0 group policy is case-sensitive": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "b",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "b",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"group policy does not match substring": {
 | 
			
		||||
		"v0 group policy does not match substring": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C"}},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "B",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "B",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"group policy matches user in group": {
 | 
			
		||||
		"v0 group policy matches user in group": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			PolicyUser:  "",
 | 
			
		||||
			PolicyGroup: "B",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "",
 | 
			
		||||
				Group: "B",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"user and group policy requires user match": {
 | 
			
		||||
		"v0 user and group policy requires user match": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "B",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "B",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"user and group policy requires group match": {
 | 
			
		||||
		"v0 user and group policy requires group match": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "D",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "D",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"user and group policy matches": {
 | 
			
		||||
		"v0 user and group policy matches": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			PolicyUser:  "Foo",
 | 
			
		||||
			PolicyGroup: "B",
 | 
			
		||||
			Policy: &v0.Policy{
 | 
			
		||||
				User:  "Foo",
 | 
			
		||||
				Group: "B",
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"v1 empty policy does not match unauthed user": {
 | 
			
		||||
			User: user.DefaultInfo{},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 empty policy does not match authed user": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo"},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 empty policy does not match authed user with groups": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"v1 user policy does not match unauthed user": {
 | 
			
		||||
			User: user.DefaultInfo{},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 user policy does not match different user": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Bar"},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 user policy is case-sensitive": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "foo"},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 user policy does not match substring": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "FooBar"},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 user policy matches username": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo"},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"v1 group policy does not match unauthed user": {
 | 
			
		||||
			User: user.DefaultInfo{},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "Foo",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 group policy does not match user in different group": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "A",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 group policy is case-sensitive": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "b",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 group policy does not match substring": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "B",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 group policy matches user in group": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "",
 | 
			
		||||
					Group: "B",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		"v1 user and group policy requires user match": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "B",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 user and group policy requires group match": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "D",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: false,
 | 
			
		||||
		},
 | 
			
		||||
		"v1 user and group policy matches": {
 | 
			
		||||
			User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
 | 
			
		||||
			Policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:  "Foo",
 | 
			
		||||
					Group: "B",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			ExpectMatch: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for k, tc := range testCases {
 | 
			
		||||
		policy := &api.Policy{}
 | 
			
		||||
		if err := api.Scheme.Convert(tc.Policy, policy); err != nil {
 | 
			
		||||
			t.Errorf("%s: error converting: %v", k, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		attr := authorizer.AttributesRecord{
 | 
			
		||||
			User: &tc.User,
 | 
			
		||||
		}
 | 
			
		||||
		actualMatch := policy{User: tc.PolicyUser, Group: tc.PolicyGroup}.subjectMatches(attr)
 | 
			
		||||
		actualMatch := subjectMatches(*policy, attr)
 | 
			
		||||
		if tc.ExpectMatch != actualMatch {
 | 
			
		||||
			t.Errorf("%v: Expected actorMatches=%v but actually got=%v",
 | 
			
		||||
				k, tc.ExpectMatch, actualMatch)
 | 
			
		||||
@@ -269,27 +597,30 @@ func newWithContents(t *testing.T, contents string) (authorizer.Authorizer, erro
 | 
			
		||||
 | 
			
		||||
func TestPolicy(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		policy  policy
 | 
			
		||||
		policy  runtime.Object
 | 
			
		||||
		attr    authorizer.Attributes
 | 
			
		||||
		matches bool
 | 
			
		||||
		name    string
 | 
			
		||||
	}{
 | 
			
		||||
		// v0
 | 
			
		||||
		{
 | 
			
		||||
			policy:  policy{},
 | 
			
		||||
			policy:  &v0.Policy{},
 | 
			
		||||
			attr:    authorizer.AttributesRecord{},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "null",
 | 
			
		||||
			name:    "v0 null",
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// v0 mismatches
 | 
			
		||||
		{
 | 
			
		||||
			policy: policy{
 | 
			
		||||
			policy: &v0.Policy{
 | 
			
		||||
				Readonly: true,
 | 
			
		||||
			},
 | 
			
		||||
			attr:    authorizer.AttributesRecord{},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "read-only mismatch",
 | 
			
		||||
			name:    "v0 read-only mismatch",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: policy{
 | 
			
		||||
			policy: &v0.Policy{
 | 
			
		||||
				User: "foo",
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
@@ -298,20 +629,21 @@ func TestPolicy(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "user name mis-match",
 | 
			
		||||
			name:    "v0 user name mis-match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: policy{
 | 
			
		||||
			policy: &v0.Policy{
 | 
			
		||||
				Resource: "foo",
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Resource:        "bar",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "resource mis-match",
 | 
			
		||||
			name:    "v0 resource mis-match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: policy{
 | 
			
		||||
			policy: &v0.Policy{
 | 
			
		||||
				User:      "foo",
 | 
			
		||||
				Resource:  "foo",
 | 
			
		||||
				Namespace: "foo",
 | 
			
		||||
@@ -322,25 +654,312 @@ func TestPolicy(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
				Resource:        "foo",
 | 
			
		||||
				Namespace:       "foo",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "namespace mis-match",
 | 
			
		||||
			name:    "v0 namespace mis-match",
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// v0 matches
 | 
			
		||||
		{
 | 
			
		||||
			policy:  &v0.Policy{},
 | 
			
		||||
			attr:    authorizer.AttributesRecord{ResourceRequest: true},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v0 null resource",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: policy{
 | 
			
		||||
				Namespace: "foo",
 | 
			
		||||
			policy: &v0.Policy{
 | 
			
		||||
				Readonly: true,
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Namespace: "bar",
 | 
			
		||||
				Verb: "get",
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v0 read-only match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v0.Policy{
 | 
			
		||||
				User: "foo",
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				User: &user.DefaultInfo{
 | 
			
		||||
					Name: "foo",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v0 user name match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v0.Policy{
 | 
			
		||||
				Resource: "foo",
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Resource:        "foo",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v0 resource match",
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// v1 mismatches
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "resource mis-match",
 | 
			
		||||
			name:    "v1 null",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User: "foo",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				User: &user.DefaultInfo{
 | 
			
		||||
					Name: "bar",
 | 
			
		||||
				},
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "v1 user name mis-match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:     "*",
 | 
			
		||||
					Readonly: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "v1 read-only mismatch",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:     "*",
 | 
			
		||||
					Resource: "foo",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Resource:        "bar",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "v1 resource mis-match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:      "foo",
 | 
			
		||||
					Namespace: "barr",
 | 
			
		||||
					Resource:  "baz",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				User: &user.DefaultInfo{
 | 
			
		||||
					Name: "foo",
 | 
			
		||||
				},
 | 
			
		||||
				Namespace:       "bar",
 | 
			
		||||
				Resource:        "baz",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "v1 namespace mis-match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:            "*",
 | 
			
		||||
					NonResourcePath: "/api",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Path:            "/api2",
 | 
			
		||||
				ResourceRequest: false,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "v1 non-resource mis-match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:            "*",
 | 
			
		||||
					NonResourcePath: "/api/*",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Path:            "/api2/foo",
 | 
			
		||||
				ResourceRequest: false,
 | 
			
		||||
			},
 | 
			
		||||
			matches: false,
 | 
			
		||||
			name:    "v1 non-resource wildcard subpath mis-match",
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// v1 matches
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User: "foo",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				User: &user.DefaultInfo{
 | 
			
		||||
					Name: "foo",
 | 
			
		||||
				},
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 user match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User: "*",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 user wildcard match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					Group: "bar",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				User: &user.DefaultInfo{
 | 
			
		||||
					Name:   "foo",
 | 
			
		||||
					Groups: []string{"bar"},
 | 
			
		||||
				},
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 group match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					Group: "*",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				User: &user.DefaultInfo{
 | 
			
		||||
					Name:   "foo",
 | 
			
		||||
					Groups: []string{"bar"},
 | 
			
		||||
				},
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 group wildcard match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:     "*",
 | 
			
		||||
					Readonly: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Verb:            "get",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 read-only match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:     "*",
 | 
			
		||||
					Resource: "foo",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Resource:        "foo",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 resource match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
					Resource:  "baz",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				User: &user.DefaultInfo{
 | 
			
		||||
					Name: "foo",
 | 
			
		||||
				},
 | 
			
		||||
				Namespace:       "bar",
 | 
			
		||||
				Resource:        "baz",
 | 
			
		||||
				ResourceRequest: true,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 namespace match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:            "*",
 | 
			
		||||
					NonResourcePath: "/api",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Path:            "/api",
 | 
			
		||||
				ResourceRequest: false,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 non-resource match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:            "*",
 | 
			
		||||
					NonResourcePath: "*",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Path:            "/api",
 | 
			
		||||
				ResourceRequest: false,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 non-resource wildcard match",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			policy: &v1beta1.Policy{
 | 
			
		||||
				Spec: v1beta1.PolicySpec{
 | 
			
		||||
					User:            "*",
 | 
			
		||||
					NonResourcePath: "/api/*",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			attr: authorizer.AttributesRecord{
 | 
			
		||||
				Path:            "/api/foo",
 | 
			
		||||
				ResourceRequest: false,
 | 
			
		||||
			},
 | 
			
		||||
			matches: true,
 | 
			
		||||
			name:    "v1 non-resource wildcard subpath match",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		matches := test.policy.matches(test.attr)
 | 
			
		||||
		policy := &api.Policy{}
 | 
			
		||||
		if err := api.Scheme.Convert(test.policy, policy); err != nil {
 | 
			
		||||
			t.Errorf("%s: error converting: %v", test.name, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		matches := matches(*policy, test.attr)
 | 
			
		||||
		if test.matches != matches {
 | 
			
		||||
			t.Errorf("unexpected value for %s, expected: %t, saw: %t", test.name, test.matches, matches)
 | 
			
		||||
			t.Errorf("%s: expected: %t, saw: %t", test.name, test.matches, matches)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
{"user":"admin"}
 | 
			
		||||
{"user":"scheduler", "readonly": true, "resource": "pods"}
 | 
			
		||||
{"user":"scheduler", "resource": "bindings"}
 | 
			
		||||
{"user":"kubelet",  "readonly": true, "resource": "pods"}
 | 
			
		||||
{"user":"kubelet",  "readonly": true, "resource": "services"}
 | 
			
		||||
{"user":"kubelet",  "readonly": true, "resource": "endpoints"}
 | 
			
		||||
{"user":"kubelet", "resource": "events"}
 | 
			
		||||
{"user":"alice", "namespace": "projectCaribou"}
 | 
			
		||||
{"user":"bob", "readonly": true, "namespace": "projectCaribou"}
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"*",         "nonResourcePath": "*", "readonly": true}
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"admin",     "namespace": "*",              "resource": "*",         "apiGroup": "*"                   }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"scheduler", "namespace": "*",              "resource": "pods",                       "readonly": true }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"scheduler", "namespace": "*",              "resource": "bindings"                                     }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet",   "namespace": "*",              "resource": "pods",                       "readonly": true }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet",   "namespace": "*",              "resource": "services",                   "readonly": true }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet",   "namespace": "*",              "resource": "endpoints",                  "readonly": true }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet",   "namespace": "*",              "resource": "events"                                       }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"alice",     "namespace": "projectCaribou", "resource": "*",         "apiGroup": "*"                   }
 | 
			
		||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"bob",       "namespace": "projectCaribou", "resource": "*",         "apiGroup": "*", "readonly": true }
 | 
			
		||||
@@ -50,6 +50,13 @@ type Attributes interface {
 | 
			
		||||
 | 
			
		||||
	// The group of the resource, if a request is for a REST object.
 | 
			
		||||
	GetAPIGroup() string
 | 
			
		||||
 | 
			
		||||
	// IsResourceRequest returns true for requests to API resources, like /api/v1/nodes,
 | 
			
		||||
	// and false for non-resource endpoints like /api, /healthz, and /swaggerapi
 | 
			
		||||
	IsResourceRequest() bool
 | 
			
		||||
 | 
			
		||||
	// GetPath returns the path of the request
 | 
			
		||||
	GetPath() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Authorizer makes an authorization decision based on information gained by making
 | 
			
		||||
@@ -77,6 +84,8 @@ type AttributesRecord struct {
 | 
			
		||||
	Namespace       string
 | 
			
		||||
	APIGroup        string
 | 
			
		||||
	Resource        string
 | 
			
		||||
	ResourceRequest bool
 | 
			
		||||
	Path            string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a AttributesRecord) GetUserName() string {
 | 
			
		||||
@@ -106,3 +115,11 @@ func (a AttributesRecord) GetResource() string {
 | 
			
		||||
func (a AttributesRecord) GetAPIGroup() string {
 | 
			
		||||
	return a.APIGroup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a AttributesRecord) IsResourceRequest() bool {
 | 
			
		||||
	return a.ResourceRequest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a AttributesRecord) GetPath() string {
 | 
			
		||||
	return a.Path
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user