mirror of https://github.com/k3s-io/k3s
modify policy to correctly identify resource versus kind
parent
1c9216a45e
commit
8a2fe9bd2b
|
@ -10,7 +10,7 @@ readonly port is not currently subject to authorization, but is planned to be
|
|||
removed soon.)
|
||||
|
||||
The authorization check for any request compares attributes of the context of
|
||||
the request, (such as user, resource kind, and namespace) with access
|
||||
the request, (such as user, resource, and namespace) with access
|
||||
policies. An API call must be allowed by some policy in order to proceed.
|
||||
|
||||
The following implementations are available, and are selected by flag:
|
||||
|
@ -28,10 +28,10 @@ The following implementations are available, and are selected by flag:
|
|||
A request has 4 attributes that can be considered for authorization:
|
||||
- user (the user-string which a user was authenticated as).
|
||||
- whether the request is readonly (GETs are readonly)
|
||||
- what kind of object is being accessed
|
||||
- what resource is being accessed
|
||||
- applies only to the API endpoints, such as
|
||||
`/api/v1beta1/pods`. For miscelaneous endpoints, like `/version`, the
|
||||
kind is the empty string.
|
||||
resource is the empty string.
|
||||
- the namespace of the object being access, or the empty string if the
|
||||
endpoint does not support namespaced objects.
|
||||
|
||||
|
@ -49,7 +49,7 @@ Each line is a "policy object". A policy object is a map with the following pro
|
|||
- `user`, type string; the user-string from `--token_auth_file`
|
||||
- `readonly`, type boolean, when true, means that the policy only applies to GET
|
||||
operations.
|
||||
- `kind`, type string; a kind of object, from an URL, such as `pods`.
|
||||
- `resource`, type string; a resource from an URL, such as `pods`.
|
||||
- `namespace`, type string; a namespace string.
|
||||
|
||||
An unset property is the same as a property set to the zero value for its type (e.g. empty string, 0, false).
|
||||
|
@ -76,9 +76,9 @@ To permit an action Policy with an unset namespace applies regardless of namespa
|
|||
|
||||
### Examples
|
||||
1. Alice can do anything: `{"user":"alice"}`
|
||||
2. Kubelet can read any pods: `{"user":"kubelet", "kind": "pods", "readonly": true}`
|
||||
3. Kubelet can read and write events: `{"user":"kubelet", "kind": "events"}`
|
||||
4. Bob can just read pods in namespace "projectCaribou": `{"user":"bob", "kind": "pods", "readonly": true, "ns": "projectCaribou"}`
|
||||
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, "ns": "projectCaribou"}`
|
||||
|
||||
[Complete file example](../pkg/auth/authorizer/abac/example_policy_file.jsonl)
|
||||
|
||||
|
|
|
@ -176,8 +176,8 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
|||
apiRequestInfo, _ := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
|
||||
// If a path follows the conventions of the REST object store, then
|
||||
// we can extract the object Kind. Otherwise, not.
|
||||
attribs.Kind = apiRequestInfo.Resource
|
||||
// we can extract the resource. Otherwise, not.
|
||||
attribs.Resource = apiRequestInfo.Resource
|
||||
|
||||
// If the request specifies a namespace, then the namespace is filled in.
|
||||
// Assumes there is no empty string namespace. Unspecified results
|
||||
|
|
|
@ -52,7 +52,7 @@ type policy struct {
|
|||
|
||||
// TODO: make this a proper REST object with its own registry.
|
||||
Readonly bool `json:"readonly,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// TODO: "expires" string in RFC3339 format.
|
||||
|
@ -100,7 +100,7 @@ func NewFromFile(path string) (policyList, error) {
|
|||
func (p policy) matches(a authorizer.Attributes) bool {
|
||||
if p.subjectMatches(a) {
|
||||
if p.Readonly == false || (p.Readonly == a.IsReadOnly()) {
|
||||
if p.Kind == "" || (p.Kind == a.GetKind()) {
|
||||
if p.Resource == "" || (p.Resource == a.GetResource()) {
|
||||
if p.Namespace == "" || (p.Namespace == a.GetNamespace()) {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -76,49 +76,49 @@ func NotTestAuthorize(t *testing.T) {
|
|||
testCases := []struct {
|
||||
User user.DefaultInfo
|
||||
RO bool
|
||||
Kind string
|
||||
Resource string
|
||||
NS string
|
||||
ExpectAllow bool
|
||||
}{
|
||||
// Scheduler can read pods
|
||||
{User: uScheduler, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Kind: "pods", NS: "", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "pods", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "pods", NS: "", ExpectAllow: true},
|
||||
// Scheduler cannot write pods
|
||||
{User: uScheduler, RO: false, Kind: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uScheduler, RO: false, Kind: "pods", NS: "", ExpectAllow: false},
|
||||
{User: uScheduler, RO: false, Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uScheduler, RO: false, Resource: "pods", NS: "", ExpectAllow: false},
|
||||
// Scheduler can write bindings
|
||||
{User: uScheduler, RO: true, Kind: "bindings", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Kind: "bindings", NS: "", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "bindings", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "bindings", NS: "", ExpectAllow: true},
|
||||
|
||||
// Alice can read and write anything in the right namespace.
|
||||
{User: uAlice, RO: true, Kind: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Kind: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Kind: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Kind: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Kind: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Kind: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Resource: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Resource: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
// .. but not the wrong namespace.
|
||||
{User: uAlice, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Kind: "widgets", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Kind: "", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Resource: "widgets", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Resource: "", NS: "ns1", ExpectAllow: false},
|
||||
|
||||
// Chuck can read events, since anyone can.
|
||||
{User: uChuck, RO: true, Kind: "events", NS: "ns1", ExpectAllow: true},
|
||||
{User: uChuck, RO: true, Kind: "events", NS: "", ExpectAllow: true},
|
||||
{User: uChuck, RO: true, Resource: "events", NS: "ns1", ExpectAllow: true},
|
||||
{User: uChuck, RO: true, Resource: "events", NS: "", ExpectAllow: true},
|
||||
// Chuck can't do other things.
|
||||
{User: uChuck, RO: false, Kind: "events", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Kind: "floop", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: false, Resource: "events", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, 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 miscelaneous endpoints, such as
|
||||
// /healthz, /version, etc.
|
||||
{User: uChuck, RO: true, Kind: "", NS: "", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Resource: "", NS: "", ExpectAllow: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
attr := authorizer.AttributesRecord{
|
||||
User: &tc.User,
|
||||
ReadOnly: tc.RO,
|
||||
Kind: tc.Kind,
|
||||
Resource: tc.Resource,
|
||||
Namespace: tc.NS,
|
||||
}
|
||||
t.Logf("tc: %v -> attr %v", tc, attr)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{"user":"admin"}
|
||||
{"user":"scheduler", "readonly": true, "kind": "pods"}
|
||||
{"user":"scheduler", "kind": "bindings"}
|
||||
{"user":"kubelet", "readonly": true, "kind": "pods"}
|
||||
{"user":"kubelet", "readonly": true, "kind": "services"}
|
||||
{"user":"kubelet", "readonly": true, "kind": "endpoints"}
|
||||
{"user":"kubelet", "kind": "events"}
|
||||
{"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", "ns": "projectCaribou"}
|
||||
{"user":"bob", "readonly": true, "ns": "projectCaribou"}
|
||||
|
|
|
@ -40,7 +40,7 @@ type Attributes interface {
|
|||
GetNamespace() string
|
||||
|
||||
// The kind of object, if a request is for a REST object.
|
||||
GetKind() string
|
||||
GetResource() string
|
||||
}
|
||||
|
||||
// Authorizer makes an authorization decision based on information gained by making
|
||||
|
@ -55,7 +55,7 @@ type AttributesRecord struct {
|
|||
User user.Info
|
||||
ReadOnly bool
|
||||
Namespace string
|
||||
Kind string
|
||||
Resource string
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetUserName() string {
|
||||
|
@ -74,6 +74,6 @@ func (a AttributesRecord) GetNamespace() string {
|
|||
return a.Namespace
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetKind() string {
|
||||
return a.Kind
|
||||
func (a AttributesRecord) GetResource() string {
|
||||
return a.Resource
|
||||
}
|
||||
|
|
|
@ -691,7 +691,7 @@ func TestKindAuthorization(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
a := newAuthorizerWithContents(t, `{"kind": "services"}
|
||||
a := newAuthorizerWithContents(t, `{"resource": "services"}
|
||||
`)
|
||||
|
||||
var m *master.Master
|
||||
|
|
Loading…
Reference in New Issue