func (ka *allowTestAuthorizer) Authorize(a authorizer.Attributes) (string, error) { var ( tenantName string ns *api.Namespace err error ) if authorizer.IsWhiteListedUser(a.GetUserName()) { return "", nil } else { if !a.IsReadOnly() && a.GetResource() == "tenants" { return "", errors.New("only admin can write tenant") } } if a.GetNamespace() != "" { ns, err = ka.kubeClient.Namespaces().Get(a.GetNamespace()) if err != nil { glog.Error(err) return "", err } tenantName = ns.Tenant } else { if a.GetTenant() != "" { te, err := ka.kubeClient.Tenants().Get(a.GetTenant()) if err != nil { glog.Error(err) return "", err } tenantName = te.Name } } if tenantName == "" || tenantName == TenantTest { return TenantTest, nil } return "", errors.New("Keystone authorization failed") }
// getResourceHandler is an HTTP handler function for get requests. It delegates to the // passed-in getterFunc to perform the actual get. func getResourceHandler(scope RequestScope, getter getterFunc) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) result, err := getter(ctx, name, req) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } // userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { tenant := api.TenantValue(ctx) if objTenant, err := scope.Namer.ObjectTenant(result); err == nil { if objTenant != tenant && tenant != "" && objTenant != "" { forbidden(w, req.Request) return } } } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
// ValidateUpdate is the default update validation for an end user. func (namespaceStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList { var admin bool = true userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { admin = false } errorList := validation.ValidateNamespace(obj.(*api.Namespace), admin) return append(errorList, validation.ValidateNamespaceUpdate(obj.(*api.Namespace), old.(*api.Namespace))...) }
// Validate validates a new namespace. func (namespaceStrategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList { var admin bool = true userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { admin = false } namespace := obj.(*api.Namespace) return validation.ValidateNamespace(namespace, admin) }
// Authorizer implements authorizer.Authorize func (ka *keystoneAuthorizer) Authorize(a authorizer.Attributes) (string, error) { var ( tenantName string ns *api.Namespace err error ) if a.GetNamespace() != "" { ns, err = ka.kubeClient.Namespaces().Get(a.GetNamespace()) if err != nil { return "", err } tenantName = ns.Tenant } else { if a.GetTenant() != "" { te, err := ka.kubeClient.Tenants().Get(a.GetTenant()) if err != nil { return "", err } tenantName = te.Name } } if authorizer.IsWhiteListedUser(a.GetUserName()) { if a.GetUserName() != api.UserAdmin { return tenantName, nil } else { return api.TenantDefault, nil } } else { if !a.IsReadOnly() && a.GetResource() == "tenants" { return "", errors.New("only admin can write tenant") } } authConfig := &authConfig{ AuthUrl: ka.authUrl, Username: a.GetUserName(), Password: a.GetPassword(), } osClient, err := newOpenstackClient(authConfig) if err != nil { glog.Errorf("%v", err) return "", err } tenant, err := osClient.getTenant() if err != nil { glog.Errorf("%v", err) return "", err } if tenantName == "" || tenantName == tenant.Name { return tenant.Name, nil } return "", errors.New("Keystone authorization failed") }
// DeleteResource returns a function that will handle a resource deletion func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) options := &api.DeleteOptions{} if checkBody { body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } if len(body) > 0 { if err := scope.Codec.DecodeInto(body, options); err != nil { errorJSON(err, scope.Codec, w) return } } } if admit != nil && admit.Handles(admission.Delete) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { if scope.Kind == "Node" || scope.Kind == "ComponentStatus" { forbidden(w, req.Request) return } } result, err := finishRequest(timeout, func() (runtime.Object, error) { return r.Delete(ctx, name, options) }) if err != nil { errorJSON(err, scope.Codec, w) return } // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid // object with the response. if result == nil { result = &unversioned.Status{ Status: unversioned.StatusSuccess, Code: http.StatusOK, Details: &unversioned.StatusDetails{ Name: name, Kind: scope.Kind, }, } } else { // when a non-status response is returned, set the self link if _, ok := result.(*unversioned.Status); !ok { if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } } } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
// UpdateResource returns a function that will handle a resource update func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() if err := scope.Codec.DecodeIntoWithSpecifiedVersionKind(body, obj, scope.APIVersion, scope.Kind); err != nil { err = transformDecodeError(typer, err, obj, body) errorJSON(err, scope.Codec, w) return } if err := checkName(obj, name, namespace, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } if admit != nil && admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { if scope.Kind == "Node" || scope.Kind == "ComponentStatus" { forbidden(w, req.Request) return } if objTenant, err := scope.Namer.ObjectTenant(obj); err == nil { tenant := api.TenantValue(ctx) if objTenant != tenant && tenant != "" && objTenant != "" { forbidden(w, req.Request) return } } } wasCreated := false result, err := finishRequest(timeout, func() (runtime.Object, error) { obj, created, err := r.Update(ctx, obj) wasCreated = created return obj, err }) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } status := http.StatusOK if wasCreated { status = http.StatusCreated } writeJSON(status, scope.Codec, result, w, isPrettyPrint(req.Request)) } }
// PatchResource returns a function that will handle a resource patch // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // TODO: we either want to remove timeout or document it (if we // document, move timeout out of this function and declare it in // api_installer) timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) // PATCH requires same permission as UPDATE if admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } versionedObj, err := converter.ConvertToVersion(r.New(), scope.APIVersion) if err != nil { errorJSON(err, scope.Codec, w) return } contentType := req.HeaderParameter("Content-Type") // Remove "; charset=" if included in header. if idx := strings.Index(contentType, ";"); idx > 0 { contentType = contentType[:idx] } patchType := api.PatchType(contentType) patchJS, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { if scope.Kind == "Node" || scope.Kind == "ComponentStatus" { forbidden(w, req.Request) return } if objTenant, err := scope.Namer.ObjectTenant(obj); err == nil { tenant := api.TenantValue(ctx) if objTenant != tenant && tenant != "" && objTenant != "" { forbidden(w, req.Request) return } } } result, err := patchResource(ctx, timeout, versionedObj, r, name, patchType, patchJS, scope.Namer, scope.Codec) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) var ( namespace, name string err error ) if includeName { namespace, name, err = scope.Namer.Name(req) } else { namespace, err = scope.Namer.Namespace(req) } if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() if err := scope.Codec.DecodeIntoWithSpecifiedVersionKind(body, obj, scope.APIVersion, scope.Kind); err != nil { err = transformDecodeError(typer, err, obj, body) errorJSON(err, scope.Codec, w) return } if admit != nil && admit.Handles(admission.Create) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { if scope.Kind == "Node" || scope.Kind == "ComponentStatus" { forbidden(w, req.Request) return } } if objTenant, err := scope.Namer.ObjectTenant(obj); err == nil { tenant := api.TenantValue(ctx) if objTenant != tenant && tenant != "" && objTenant != "" { if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { forbidden(w, req.Request) return } } if objTenant == "" { scope.Namer.SetTenant(obj, tenant) } if scope.Kind == "Tenant" { if _, objName, err := scope.Namer.ObjectName(obj); err == nil { scope.Namer.SetTenant(obj, objName) } else { scope.Namer.SetTenant(obj, "") } } } result, err := finishRequest(timeout, func() (runtime.Object, error) { out, err := r.Create(ctx, name, obj) if status, ok := out.(*unversioned.Status); ok && err == nil && status.Code == 0 { status.Code = http.StatusCreated } return out, err }) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusCreated, scope.APIVersion, scope.Codec, result, w, req.Request) } }
// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object. func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, err := scope.Namer.Namespace(req) if err != nil { errorJSON(err, scope.Codec, w) return } // Watches for single objects are routed to this function. // Treat a /name parameter the same as a field selector entry. hasName := true _, name, err := scope.Namer.Name(req) if err != nil { hasName = false } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) out, err := queryToObject(req.Request.URL.Query(), scope, "ListOptions") if err != nil { errorJSON(err, scope.Codec, w) return } opts := *out.(*api.ListOptions) // transform fields // TODO: queryToObject should do this. fn := func(label, value string) (newLabel, newValue string, err error) { return scope.Convertor.ConvertFieldLabel(scope.APIVersion, scope.Kind, label, value) } if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { // TODO: allow bad request to set field causes based on query parameters err = errors.NewBadRequest(err.Error()) errorJSON(err, scope.Codec, w) return } if hasName { // metadata.name is the canonical internal name. // generic.SelectionPredicate will notice that this is // a request for a single object and optimize the // storage query accordingly. nameSelector := fields.OneTermEqualSelector("metadata.name", name) if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { // It doesn't make sense to ask for both a name // and a field selector, since just the name is // sufficient to narrow down the request to a // single object. errorJSON( errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), scope.Codec, w, ) return } opts.FieldSelector = nameSelector } if (opts.Watch || forceWatch) && rw != nil { watcher, err := rw.Watch(ctx, &opts) if err != nil { errorJSON(err, scope.Codec, w) return } // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=. timeout := time.Duration(0) if opts.TimeoutSeconds != nil { timeout = time.Duration(*opts.TimeoutSeconds) * time.Second } if timeout == 0 && minRequestTimeout > 0 { timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) } serveWatch(watcher, scope, w, req, timeout) return } result, err := r.List(ctx, &opts) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setListSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } userinfo, ok := api.UserFrom(ctx) if ok && !authorizer.IsWhiteListedUser(userinfo.GetName()) { if scope.Kind == "Node" || scope.Kind == "ComponentStatus" { forbidden(w, req.Request) return } tenant := api.TenantValue(ctx) if err := filterListInTenant(result, tenant, scope.Kind, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }