From bfcad0d1b124f194ba52f0288be6a5f7cb6bf592 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Mon, 18 Dec 2017 13:56:50 -0700 Subject: [PATCH] Enable access control --- api/server.go | 16 +++++++++++++++- api/writer/json.go | 2 +- authorization/all.go | 35 +++++++++++++++++++++-------------- httperror/error.go | 1 + store/proxy/proxy_store.go | 13 ++++++++++--- types/server_types.go | 9 +++++++-- 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/api/server.go b/api/server.go index 901ed083..fb24a011 100644 --- a/api/server.go +++ b/api/server.go @@ -31,6 +31,7 @@ type Server struct { StoreWrapper StoreWrapper URLParser parse.URLParser Defaults Defaults + AccessControl types.AccessControl } type Defaults struct { @@ -53,6 +54,7 @@ func NewAPIServer() *Server { }, SubContextAttributeProvider: &parse.DefaultSubContextAttributeProvider{}, Resolver: parse.DefaultResolver, + AccessControl: &authorization.AllAccess{}, Defaults: Defaults{ CreateHandler: handler.CreateHandler, DeleteHandler: handler.DeleteHandler, @@ -88,7 +90,7 @@ func (s *Server) parser(rw http.ResponseWriter, req *http.Request) (*types.APICo ctx.SubContextAttributeProvider = s.SubContextAttributeProvider } - ctx.AccessControl = &authorization.AllAccess{} + ctx.AccessControl = s.AccessControl return ctx, err } @@ -186,12 +188,24 @@ func (s *Server) handle(rw http.ResponseWriter, req *http.Request) (*types.APICo if apiRequest.Link == "" { switch apiRequest.Method { case http.MethodGet: + if !apiRequest.AccessControl.CanList(apiRequest, apiRequest.Schema) { + return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not list "+apiRequest.Schema.Type) + } handler = apiRequest.Schema.ListHandler case http.MethodPost: + if !apiRequest.AccessControl.CanCreate(apiRequest, apiRequest.Schema) { + return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not create "+apiRequest.Schema.Type) + } handler = apiRequest.Schema.CreateHandler case http.MethodPut: + if !apiRequest.AccessControl.CanUpdate(apiRequest, apiRequest.Schema) { + return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not update "+apiRequest.Schema.Type) + } handler = apiRequest.Schema.UpdateHandler case http.MethodDelete: + if !apiRequest.AccessControl.CanDelete(apiRequest, apiRequest.Schema) { + return apiRequest, httperror.NewAPIError(httperror.PermissionDenied, "Can not delete "+apiRequest.Schema.Type) + } handler = apiRequest.Schema.DeleteHandler } } else { diff --git a/api/writer/json.go b/api/writer/json.go index 30af00fb..3e9ed20c 100644 --- a/api/writer/json.go +++ b/api/writer/json.go @@ -158,7 +158,7 @@ func newCollection(apiContext *types.APIContext) *types.GenericCollection { } if apiContext.Method == http.MethodGet { - if apiContext.AccessControl.CanCreate(apiContext.Schema) { + if apiContext.AccessControl.CanCreate(apiContext, apiContext.Schema) { result.CreateTypes[apiContext.Schema.ID] = apiContext.URLBuilder.Collection(apiContext.Schema, apiContext.Version) } } diff --git a/authorization/all.go b/authorization/all.go index db736f80..1fa09934 100644 --- a/authorization/all.go +++ b/authorization/all.go @@ -4,25 +4,32 @@ import ( "net/http" "github.com/rancher/norman/types" + "github.com/rancher/norman/types/slice" ) type AllAccess struct { } -func (*AllAccess) CanCreate(schema *types.Schema) bool { - for _, method := range schema.CollectionMethods { - if method == http.MethodPost { - return true - } - } - return false +func (*AllAccess) CanCreate(apiContext *types.APIContext, schema *types.Schema) bool { + return slice.ContainsString(schema.CollectionMethods, http.MethodPost) } -func (*AllAccess) CanList(schema *types.Schema) bool { - for _, method := range schema.CollectionMethods { - if method == http.MethodGet { - return true - } - } - return false +func (*AllAccess) CanList(apiContext *types.APIContext, schema *types.Schema) bool { + return slice.ContainsString(schema.CollectionMethods, http.MethodGet) +} + +func (*AllAccess) CanUpdate(apiContext *types.APIContext, schema *types.Schema) bool { + return slice.ContainsString(schema.ResourceMethods, http.MethodPut) +} + +func (*AllAccess) CanDelete(apiContext *types.APIContext, schema *types.Schema) bool { + return slice.ContainsString(schema.ResourceMethods, http.MethodDelete) +} + +func (*AllAccess) Filter(apiContext *types.APIContext, obj map[string]interface{}, context map[string]string) map[string]interface{} { + return obj +} + +func (*AllAccess) FilterList(apiContext *types.APIContext, obj []map[string]interface{}, context map[string]string) []map[string]interface{} { + return obj } diff --git a/httperror/error.go b/httperror/error.go index 022ec4d9..91f6c738 100644 --- a/httperror/error.go +++ b/httperror/error.go @@ -24,6 +24,7 @@ var ( ActionNotAvailable = ErrorCode{"ActionNotAvailable", 404} InvalidState = ErrorCode{"InvalidState", 422} ServerError = ErrorCode{"ServerError", 500} + PermissionDenied = ErrorCode{"PermissionDenied", 403} MethodNotAllowed = ErrorCode{"MethodNotAllow", 405} NotFound = ErrorCode{"NotFound", 404} diff --git a/store/proxy/proxy_store.go b/store/proxy/proxy_store.go index 61be093c..0ec04ac5 100644 --- a/store/proxy/proxy_store.go +++ b/store/proxy/proxy_store.go @@ -4,6 +4,8 @@ import ( ejson "encoding/json" "strings" + "net/http" + "github.com/rancher/norman/types" "github.com/rancher/norman/types/convert" "github.com/rancher/norman/types/values" @@ -33,6 +35,7 @@ type Store struct { version string kind string resourcePlural string + authContext map[string]string } func NewProxyStore(k8sClient rest.Interface, @@ -44,12 +47,16 @@ func NewProxyStore(k8sClient rest.Interface, version: version, kind: kind, resourcePlural: resourcePlural, + authContext: map[string]string{ + "apiGroup": group, + "resource": resourcePlural, + }, } } func (p *Store) doAuthed(apiContext *types.APIContext, request *rest.Request) rest.Result { for _, header := range authHeaders { - request.SetHeader(header, apiContext.Request.Header[header]...) + request.SetHeader(header, apiContext.Request.Header[http.CanonicalHeaderKey(header)]...) } return request.Do() } @@ -85,7 +92,7 @@ func (p *Store) List(apiContext *types.APIContext, schema *types.Schema, opt typ result = append(result, p.fromInternal(schema, obj.Object)) } - return result, nil + return apiContext.AccessControl.FilterList(apiContext, result, p.authContext), nil } func (p *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt types.QueryOptions) (chan map[string]interface{}, error) { @@ -115,7 +122,7 @@ func (p *Store) Watch(apiContext *types.APIContext, schema *types.Schema, opt ty for event := range watcher.ResultChan() { data := event.Object.(*unstructured.Unstructured) p.fromInternal(schema, data.Object) - result <- data.Object + result <- apiContext.AccessControl.Filter(apiContext, data.Object, p.authContext) } close(result) }() diff --git a/types/server_types.go b/types/server_types.go index c668b56b..1ad12e98 100644 --- a/types/server_types.go +++ b/types/server_types.go @@ -61,8 +61,13 @@ type ResponseWriter interface { } type AccessControl interface { - CanCreate(schema *Schema) bool - CanList(schema *Schema) bool + CanCreate(apiContext *APIContext, schema *Schema) bool + CanList(apiContext *APIContext, schema *Schema) bool + CanUpdate(apiContext *APIContext, schema *Schema) bool + CanDelete(apiContext *APIContext, schema *Schema) bool + + Filter(apiContext *APIContext, obj map[string]interface{}, context map[string]string) map[string]interface{} + FilterList(apiContext *APIContext, obj []map[string]interface{}, context map[string]string) []map[string]interface{} } type APIContext struct {