diff --git a/pkg/authorization/authorizer/attributes_builder.go b/pkg/authorization/authorizer/attributes_builder.go deleted file mode 100644 index 5bf06a9c3e25..000000000000 --- a/pkg/authorization/authorizer/attributes_builder.go +++ /dev/null @@ -1,53 +0,0 @@ -package authorizer - -import ( - "errors" - "net/http" - - "k8s.io/apiserver/pkg/authorization/authorizer" - apirequest "k8s.io/apiserver/pkg/endpoints/request" -) - -type openshiftAuthorizationAttributeBuilder struct { - contextMapper apirequest.RequestContextMapper - infoFactory RequestInfoFactory -} - -func NewAuthorizationAttributeBuilder(contextMapper apirequest.RequestContextMapper, infoFactory RequestInfoFactory) AuthorizationAttributeBuilder { - return &openshiftAuthorizationAttributeBuilder{contextMapper, infoFactory} -} - -func (a *openshiftAuthorizationAttributeBuilder) GetAttributes(req *http.Request) (authorizer.Attributes, error) { - - ctx, ok := a.contextMapper.Get(req) - if !ok { - return nil, errors.New("no context found for request") - } - - user, ok := apirequest.UserFrom(ctx) - if !ok { - return nil, errors.New("no user found on context") - } - - requestInfo, err := a.infoFactory.NewRequestInfo(req) - if err != nil { - return nil, err - } - - attribs := authorizer.AttributesRecord{ - User: user, - - ResourceRequest: requestInfo.IsResourceRequest, - Path: requestInfo.Path, - Verb: requestInfo.Verb, - - APIGroup: requestInfo.APIGroup, - APIVersion: requestInfo.APIVersion, - Resource: requestInfo.Resource, - Subresource: requestInfo.Subresource, - Namespace: requestInfo.Namespace, - Name: requestInfo.Name, - } - - return attribs, nil -} diff --git a/pkg/authorization/authorizer/browser_safe_request_info_resolver.go b/pkg/authorization/authorizer/browser_safe_request_info_resolver.go index a8add982ca3a..06d3432e5d91 100644 --- a/pkg/authorization/authorizer/browser_safe_request_info_resolver.go +++ b/pkg/authorization/authorizer/browser_safe_request_info_resolver.go @@ -9,7 +9,7 @@ import ( type browserSafeRequestInfoResolver struct { // infoFactory is used to determine info for the request - infoFactory RequestInfoFactory + infoFactory apirequest.RequestInfoResolver // contextMapper is used to look up the context corresponding to a request // to obtain the user associated with the request @@ -19,7 +19,7 @@ type browserSafeRequestInfoResolver struct { authenticatedGroups sets.String } -func NewBrowserSafeRequestInfoResolver(contextMapper apirequest.RequestContextMapper, authenticatedGroups sets.String, infoFactory RequestInfoFactory) RequestInfoFactory { +func NewBrowserSafeRequestInfoResolver(contextMapper apirequest.RequestContextMapper, authenticatedGroups sets.String, infoFactory apirequest.RequestInfoResolver) apirequest.RequestInfoResolver { return &browserSafeRequestInfoResolver{ contextMapper: contextMapper, authenticatedGroups: authenticatedGroups, diff --git a/pkg/authorization/authorizer/interfaces.go b/pkg/authorization/authorizer/interfaces.go index cc812fda8885..625563539a1b 100644 --- a/pkg/authorization/authorizer/interfaces.go +++ b/pkg/authorization/authorizer/interfaces.go @@ -1,25 +1,14 @@ package authorizer import ( - "net/http" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authorization/authorizer" - apirequest "k8s.io/apiserver/pkg/endpoints/request" ) type SubjectLocator interface { GetAllowedSubjects(attributes authorizer.Attributes) (sets.String, sets.String, error) } -type AuthorizationAttributeBuilder interface { - GetAttributes(request *http.Request) (authorizer.Attributes, error) -} - -type RequestInfoFactory interface { - NewRequestInfo(req *http.Request) (*apirequest.RequestInfo, error) -} - // ForbiddenMessageMaker creates a forbidden message from Attributes type ForbiddenMessageMaker interface { MakeMessage(attrs authorizer.Attributes) (string, error) diff --git a/pkg/authorization/authorizer/personal_subjectaccessreview.go b/pkg/authorization/authorizer/personal_subjectaccessreview.go index 3c0b98fe3ad7..ec576c667357 100644 --- a/pkg/authorization/authorizer/personal_subjectaccessreview.go +++ b/pkg/authorization/authorizer/personal_subjectaccessreview.go @@ -8,6 +8,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/request" + apirequest "k8s.io/apiserver/pkg/endpoints/request" kapi "k8s.io/kubernetes/pkg/api" authorizationapi "github.com/openshift/origin/pkg/authorization/apis/authorization" @@ -15,10 +16,10 @@ import ( type personalSARRequestInfoResolver struct { // infoFactory is used to determine info for the request - infoFactory RequestInfoFactory + infoFactory apirequest.RequestInfoResolver } -func NewPersonalSARRequestInfoResolver(infoFactory RequestInfoFactory) RequestInfoFactory { +func NewPersonalSARRequestInfoResolver(infoFactory apirequest.RequestInfoResolver) apirequest.RequestInfoResolver { return &personalSARRequestInfoResolver{ infoFactory: infoFactory, } diff --git a/pkg/authorization/authorizer/project_request_info_resolver.go b/pkg/authorization/authorizer/project_request_info_resolver.go index 827941d1ca6e..5834fce8f9e3 100644 --- a/pkg/authorization/authorizer/project_request_info_resolver.go +++ b/pkg/authorization/authorizer/project_request_info_resolver.go @@ -3,21 +3,21 @@ package authorizer import ( "net/http" - "k8s.io/apiserver/pkg/endpoints/request" + apirequest "k8s.io/apiserver/pkg/endpoints/request" ) type projectRequestInfoResolver struct { // infoFactory is used to determine info for the request - infoFactory RequestInfoFactory + infoFactory apirequest.RequestInfoResolver } -func NewProjectRequestInfoResolver(infoFactory RequestInfoFactory) RequestInfoFactory { +func NewProjectRequestInfoResolver(infoFactory apirequest.RequestInfoResolver) apirequest.RequestInfoResolver { return &projectRequestInfoResolver{ infoFactory: infoFactory, } } -func (a *projectRequestInfoResolver) NewRequestInfo(req *http.Request) (*request.RequestInfo, error) { +func (a *projectRequestInfoResolver) NewRequestInfo(req *http.Request) (*apirequest.RequestInfo, error) { requestInfo, err := a.infoFactory.NewRequestInfo(req) if err != nil { return requestInfo, err diff --git a/pkg/cmd/server/handlers/authorization.go b/pkg/cmd/server/handlers/authorization.go index b947017ec0d2..ac0fb53a8ffc 100644 --- a/pkg/cmd/server/handlers/authorization.go +++ b/pkg/cmd/server/handlers/authorization.go @@ -14,10 +14,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" kauthorizer "k8s.io/apiserver/pkg/authorization/authorizer" - apirequest "k8s.io/apiserver/pkg/endpoints/request" kapi "k8s.io/kubernetes/pkg/api" - - "github.com/openshift/origin/pkg/authorization/authorizer" ) type bypassAuthorizer struct { @@ -38,33 +35,6 @@ func (a bypassAuthorizer) Authorize(attributes kauthorizer.Attributes) (allowed return a.authorizer.Authorize(attributes) } -// AuthorizationFilter imposes normal authorization rules -func AuthorizationFilter(handler http.Handler, authorizer kauthorizer.Authorizer, authorizationAttributeBuilder authorizer.AuthorizationAttributeBuilder, contextMapper apirequest.RequestContextMapper) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - attributes, err := authorizationAttributeBuilder.GetAttributes(req) - if err != nil { - Forbidden(err.Error(), attributes, w, req) - return - } - if attributes == nil { - Forbidden("No attributes", attributes, w, req) - return - } - - allowed, reason, err := authorizer.Authorize(attributes) - if err != nil { - Forbidden(err.Error(), attributes, w, req) - return - } - if !allowed { - Forbidden(reason, attributes, w, req) - return - } - - handler.ServeHTTP(w, req) - }) -} - // Forbidden renders a simple forbidden error to the response func Forbidden(reason string, attributes kauthorizer.Attributes, w http.ResponseWriter, req *http.Request) { kind := "" diff --git a/pkg/cmd/server/kubernetes/master/master_config.go b/pkg/cmd/server/kubernetes/master/master_config.go index 27033b0ecd13..b0447e1fc1c4 100644 --- a/pkg/cmd/server/kubernetes/master/master_config.go +++ b/pkg/cmd/server/kubernetes/master/master_config.go @@ -59,9 +59,11 @@ import ( kversion "k8s.io/kubernetes/pkg/version" "github.com/openshift/origin/pkg/api" + oauthorizer "github.com/openshift/origin/pkg/authorization/authorizer" "github.com/openshift/origin/pkg/authorization/authorizer/scope" "github.com/openshift/origin/pkg/cmd/flagtypes" configapi "github.com/openshift/origin/pkg/cmd/server/api" + "github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" "github.com/openshift/origin/pkg/cmd/server/cm" "github.com/openshift/origin/pkg/cmd/server/crypto" "github.com/openshift/origin/pkg/cmd/server/election" @@ -449,6 +451,7 @@ func buildKubeApiserverConfig( genericConfig.DisabledPostStartHooks.Insert("extensions/third-party-resources") genericConfig.AdmissionControl = admissionControl genericConfig.RequestContextMapper = requestContextMapper + genericConfig.RequestInfoResolver = openshiftRequestInfoResolver(genericConfig.RequestContextMapper) genericConfig.OpenAPIConfig = defaultOpenAPIConfig(masterConfig) genericConfig.SwaggerConfig = apiserver.DefaultSwaggerConfig() genericConfig.SwaggerConfig.PostBuildHandler = customizeSwaggerDefinition @@ -781,3 +784,18 @@ func readCAorNil(file string) ([]byte, error) { func newMasterLeases(storage storage.Interface, masterEndpointReconcileTTL int) election.Leases { return election.NewLeases(storage, "/masterleases/", uint64(masterEndpointReconcileTTL)) } + +func openshiftRequestInfoResolver(requestContextMapper apirequest.RequestContextMapper) apirequest.RequestInfoResolver { + // Default API request info factory + requestInfoFactory := &apirequest.RequestInfoFactory{APIPrefixes: sets.NewString("api", "osapi", "oapi", "apis"), GrouplessAPIPrefixes: sets.NewString("api", "osapi", "oapi")} + // Wrap with a request info factory that detects unsafe requests and modifies verbs/resources appropriately so policy can address them separately + browserSafeRequestInfoResolver := oauthorizer.NewBrowserSafeRequestInfoResolver( + requestContextMapper, + sets.NewString(bootstrappolicy.AuthenticatedGroup), + requestInfoFactory, + ) + personalSARRequestInfoResolver := oauthorizer.NewPersonalSARRequestInfoResolver(browserSafeRequestInfoResolver) + projectRequestInfoResolver := oauthorizer.NewProjectRequestInfoResolver(personalSARRequestInfoResolver) + + return projectRequestInfoResolver +} diff --git a/pkg/cmd/server/kubernetes/master/master_config_test.go b/pkg/cmd/server/kubernetes/master/master_config_test.go index 0489b114976e..a8685c0cc740 100644 --- a/pkg/cmd/server/kubernetes/master/master_config_test.go +++ b/pkg/cmd/server/kubernetes/master/master_config_test.go @@ -35,6 +35,7 @@ var expectedGroupPreferredVersions []string = []string{ "admissionregistration.k8s.io/v1alpha1", "apps/v1beta1,authentication.k8s.io/v1", "authorization.k8s.io/v1", + "authorization.openshift.io/v1", "autoscaling/v1", "batch/v1", "certificates.k8s.io/v1beta1", diff --git a/pkg/cmd/server/origin/controller/standalone_apiserver.go b/pkg/cmd/server/origin/controller/standalone_apiserver.go index ac56513a1645..2b0d2c81fd13 100644 --- a/pkg/cmd/server/origin/controller/standalone_apiserver.go +++ b/pkg/cmd/server/origin/controller/standalone_apiserver.go @@ -19,7 +19,6 @@ import ( authzwebhook "k8s.io/apiserver/plugin/pkg/authorizer/webhook" clientgoclientset "k8s.io/client-go/kubernetes" - "github.com/openshift/origin/pkg/authorization/authorizer" configapi "github.com/openshift/origin/pkg/cmd/server/api" serverauthenticator "github.com/openshift/origin/pkg/cmd/server/authenticator" "github.com/openshift/origin/pkg/cmd/server/crypto" @@ -57,11 +56,10 @@ func RunControllerServer(servingInfo configapi.HTTPServingInfo, kubeExternal cli requestInfoResolver := apiserver.NewRequestInfoResolver(&apiserver.Config{}) // the request context mapper for controllers is always separate requestContextMapper := apirequest.NewRequestContextMapper() - authorizationAttributeBuilder := authorizer.NewAuthorizationAttributeBuilder(requestContextMapper, requestInfoResolver) // we use direct bypass to allow readiness and health to work regardless of the master health authz := serverhandlers.NewBypassAuthorizer(remoteAuthz, "/healthz", "/healthz/ready") - handler := serverhandlers.AuthorizationFilter(mux, authz, authorizationAttributeBuilder, requestContextMapper) + handler := apifilters.WithAuthorization(mux, requestContextMapper, authz) handler = apifilters.WithAuthentication(handler, requestContextMapper, authn, apifilters.Unauthorized(false)) handler = apiserverfilters.WithPanicRecovery(handler) handler = apifilters.WithRequestInfo(handler, requestInfoResolver, requestContextMapper) diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index 09c5eea15746..44ea29a0ea59 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -287,7 +287,7 @@ func (c *MasterConfig) buildHandlerChain() (func(apiHandler http.Handler, kc *ap // these are all equivalent to the kube handler chain /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - handler = serverhandlers.AuthorizationFilter(handler, c.Authorizer, c.AuthorizationAttributeBuilder, genericConfig.RequestContextMapper) + handler = apifilters.WithAuthorization(handler, c.RequestContextMapper, c.Authorizer) handler = serverhandlers.ImpersonationFilter(handler, c.Authorizer, cache.NewGroupCache(c.UserInformers.User().InternalVersion().Groups()), genericConfig.RequestContextMapper) // audit handler must comes before the impersonationFilter to read the original user if c.Options.AuditConfig.Enabled { @@ -318,7 +318,7 @@ func (c *MasterConfig) buildHandlerChain() (func(apiHandler http.Handler, kc *ap // execution - updates vs reads, long reads vs short reads, fat reads vs skinny reads. // NOTE: read vs. write is implemented in Kube 1.6+ handler = apiserverfilters.WithMaxInFlightLimit(handler, genericConfig.MaxRequestsInFlight, genericConfig.MaxMutatingRequestsInFlight, genericConfig.RequestContextMapper, genericConfig.LongRunningFunc) - handler = apifilters.WithRequestInfo(handler, apiserver.NewRequestInfoResolver(genericConfig), genericConfig.RequestContextMapper) + handler = apifilters.WithRequestInfo(handler, genericConfig.RequestInfoResolver, genericConfig.RequestContextMapper) handler = apirequest.WithRequestContext(handler, genericConfig.RequestContextMapper) handler = apiserverfilters.WithPanicRecovery(handler) /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/cmd/server/origin/master_config.go b/pkg/cmd/server/origin/master_config.go index ec6c6e4f7484..c093244b8edf 100644 --- a/pkg/cmd/server/origin/master_config.go +++ b/pkg/cmd/server/origin/master_config.go @@ -100,9 +100,6 @@ type MasterConfig struct { Authorizer kauthorizer.Authorizer SubjectLocator authorizer.SubjectLocator - // TODO(sttts): replace AuthorizationAttributeBuilder with apiserverfilters.NewRequestAttributeGetter - AuthorizationAttributeBuilder authorizer.AuthorizationAttributeBuilder - ProjectAuthorizationCache *projectauth.AuthorizationCache ProjectCache *projectcache.ProjectCache ClusterQuotaMappingController *clusterquotamapping.ClusterQuotaMappingController @@ -300,11 +297,10 @@ func BuildMasterConfig(options configapi.MasterConfig, informers InformerAccess) RESTOptionsGetter: restOptsGetter, - RuleResolver: ruleResolver, - Authenticator: authenticator, - Authorizer: authorizer, - SubjectLocator: subjectLocator, - AuthorizationAttributeBuilder: newAuthorizationAttributeBuilder(requestContextMapper), + RuleResolver: ruleResolver, + Authenticator: authenticator, + Authorizer: authorizer, + SubjectLocator: subjectLocator, ProjectAuthorizationCache: newProjectAuthorizationCache( subjectLocator, @@ -782,22 +778,6 @@ func newAuthorizer(kubeAuthorizer kauthorizer.Authorizer, kubeSubjectLocator rba return authorizer, subjectLocator } -func newAuthorizationAttributeBuilder(requestContextMapper apirequest.RequestContextMapper) authorizer.AuthorizationAttributeBuilder { - // Default API request info factory - requestInfoFactory := &apirequest.RequestInfoFactory{APIPrefixes: sets.NewString("api", "osapi", "oapi", "apis"), GrouplessAPIPrefixes: sets.NewString("api", "osapi", "oapi")} - // Wrap with a request info factory that detects unsafe requests and modifies verbs/resources appropriately so policy can address them separately - browserSafeRequestInfoResolver := authorizer.NewBrowserSafeRequestInfoResolver( - requestContextMapper, - sets.NewString(bootstrappolicy.AuthenticatedGroup), - requestInfoFactory, - ) - personalSARRequestInfoResolver := authorizer.NewPersonalSARRequestInfoResolver(browserSafeRequestInfoResolver) - projectRequestInfoResolver := authorizer.NewProjectRequestInfoResolver(personalSARRequestInfoResolver) - - authorizationAttributeBuilder := authorizer.NewAuthorizationAttributeBuilder(requestContextMapper, projectRequestInfoResolver) - return authorizationAttributeBuilder -} - // KubeClientsetInternal returns the kubernetes client object func (c *MasterConfig) KubeClientsetInternal() kclientsetinternal.Interface { return c.PrivilegedLoopbackKubernetesClientsetInternal diff --git a/test/cmd/authentication.sh b/test/cmd/authentication.sh index be5211b0cc9d..57cc571e91e7 100755 --- a/test/cmd/authentication.sh +++ b/test/cmd/authentication.sh @@ -69,7 +69,7 @@ os::cmd::expect_success_and_text "oc get user/~ --token='${allescalatingpowersto os::cmd::expect_success "oc get secrets --token='${allescalatingpowerstoken}' -n '${project}'" # scopes allow it, but authorization doesn't os::cmd::try_until_failure "oc get secrets --token='${allescalatingpowerstoken}' -n default" -os::cmd::expect_failure_and_text "oc get secrets --token='${allescalatingpowerstoken}' -n default" 'cannot list secrets in project' +os::cmd::expect_failure_and_text "oc get secrets --token='${allescalatingpowerstoken}' -n default" 'cannot list secrets in the namespace' os::cmd::expect_success_and_text "oc get projects --token='${allescalatingpowerstoken}'" "${project}" os::cmd::expect_success_and_text "oc policy can-i --list --token='${allescalatingpowerstoken}' -n '${project}'" 'get.*pods' diff --git a/test/cmd/status.sh b/test/cmd/status.sh index c003f9e75672..46a58cb4614a 100755 --- a/test/cmd/status.sh +++ b/test/cmd/status.sh @@ -44,7 +44,7 @@ os::cmd::expect_success_and_text "oc login --server=${KUBERNETES_MASTER} --certi os::cmd::expect_success_and_text 'oc status' "You don't have any projects. You can try to create a new project, by running" os::cmd::expect_success_and_text 'oc status --all-namespaces' "Showing all projects on server" # make sure `oc status` does not re-use the "no projects" message from `oc login` if -n is specified -os::cmd::expect_failure_and_text 'oc status -n forbidden' 'Error from server \(Forbidden\): User "test-user" cannot get project "forbidden"' +os::cmd::expect_failure_and_text 'oc status -n forbidden' 'Error from server \(Forbidden\): User "test-user" cannot get projects in the namespace "forbidden"' # create a new project os::cmd::expect_success "oc new-project project-bar --display-name='my project' --description='test project'" @@ -52,7 +52,7 @@ os::cmd::expect_success_and_text "oc project" 'Using project "project-bar"' # make sure `oc status` does not use "no projects" message if there is a project created os::cmd::expect_success_and_text 'oc status' "In project my project \(project-bar\) on server" -os::cmd::expect_failure_and_text 'oc status -n forbidden' 'Error from server \(Forbidden\): User "test-user" cannot get project "forbidden"' +os::cmd::expect_failure_and_text 'oc status -n forbidden' 'Error from server \(Forbidden\): User "test-user" cannot get projects in the namespace "forbidden"' # create a second project os::cmd::expect_success "oc new-project project-bar-2 --display-name='my project 2' --description='test project 2'" @@ -62,7 +62,7 @@ os::cmd::expect_success_and_text "oc project" 'Using project "project-bar-2"' # message since `project-bar` still exists os::cmd::expect_success_and_text "oc delete project project-bar-2" 'project "project-bar-2" deleted' # the deletion is asynchronous and can take a while, so wait until we see the error -os::cmd::try_until_text "oc status" 'Error from server \(Forbidden\): User "test-user" cannot get project "project-bar-2"' +os::cmd::try_until_text "oc status" 'Error from server \(Forbidden\): User "test-user" cannot get projects in the namespace "project-bar-2"' # delete "project-bar" and test that `oc status` still does not return the "no projects" message. # Although we are deleting the last remaining project, the current context's namespace is still set @@ -71,7 +71,7 @@ os::cmd::try_until_text "oc status" 'Error from server \(Forbidden\): User "test os::cmd::expect_success "oc project project-bar" os::cmd::expect_success "oc delete project project-bar" # the deletion is asynchronous and can take a while, so wait until we see the error -os::cmd::try_until_text "oc status" 'Error from server \(Forbidden\): User "test-user" cannot get project "project-bar"' +os::cmd::try_until_text "oc status" 'Error from server \(Forbidden\): User "test-user" cannot get projects in the namespace "project-bar"' os::cmd::try_until_not_text "oc get projects" "project-bar" os::cmd::try_until_not_text "oc get projects" "project-bar-2" os::cmd::expect_success "oc logout" diff --git a/test/integration/authorization_test.go b/test/integration/authorization_test.go index ca544fca11fb..f89548cb7a10 100644 --- a/test/integration/authorization_test.go +++ b/test/integration/authorization_test.go @@ -1174,8 +1174,8 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) { localReview: askCanEdgarDeletePods, kubeAuthInterface: haroldSARGetter, kubeNamespace: "mallet-project", - err: `User "harold" cannot create localsubjectaccessreviews in project "mallet-project"`, - kubeErr: `User "harold" cannot create localsubjectaccessreviews.authorization.k8s.io in project "mallet-project"`, + err: `User "harold" cannot create localsubjectaccessreviews in the namespace "mallet-project"`, + kubeErr: `User "harold" cannot create localsubjectaccessreviews.authorization.k8s.io in the namespace "mallet-project"`, }.run(t) subjectAccessReviewTest{ description: "system:anonymous denied ability to run subject access review in project mallet-project", @@ -1183,8 +1183,8 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) { localReview: askCanEdgarDeletePods, kubeAuthInterface: anonymousSARGetter, kubeNamespace: "mallet-project", - err: `User "system:anonymous" cannot create localsubjectaccessreviews in project "mallet-project"`, - kubeErr: `User "system:anonymous" cannot create localsubjectaccessreviews.authorization.k8s.io in project "mallet-project"`, + err: `User "system:anonymous" cannot create localsubjectaccessreviews in the namespace "mallet-project"`, + kubeErr: `User "system:anonymous" cannot create localsubjectaccessreviews.authorization.k8s.io in the namespace "mallet-project"`, }.run(t) // ensure message does not leak whether the namespace exists or not subjectAccessReviewTest{ @@ -1193,8 +1193,8 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) { localReview: askCanEdgarDeletePods, kubeAuthInterface: haroldSARGetter, kubeNamespace: "nonexistent-project", - err: `User "harold" cannot create localsubjectaccessreviews in project "nonexistent-project"`, - kubeErr: `User "harold" cannot create localsubjectaccessreviews.authorization.k8s.io in project "nonexistent-project"`, + err: `User "harold" cannot create localsubjectaccessreviews in the namespace "nonexistent-project"`, + kubeErr: `User "harold" cannot create localsubjectaccessreviews.authorization.k8s.io in the namespace "nonexistent-project"`, }.run(t) subjectAccessReviewTest{ description: "system:anonymous denied ability to run subject access review in project nonexistent-project", @@ -1202,8 +1202,8 @@ func TestAuthorizationSubjectAccessReview(t *testing.T) { localReview: askCanEdgarDeletePods, kubeAuthInterface: anonymousSARGetter, kubeNamespace: "nonexistent-project", - err: `User "system:anonymous" cannot create localsubjectaccessreviews in project "nonexistent-project"`, - kubeErr: `User "system:anonymous" cannot create localsubjectaccessreviews.authorization.k8s.io in project "nonexistent-project"`, + err: `User "system:anonymous" cannot create localsubjectaccessreviews in the namespace "nonexistent-project"`, + kubeErr: `User "system:anonymous" cannot create localsubjectaccessreviews.authorization.k8s.io in the namespace "nonexistent-project"`, }.run(t) askCanHaroldUpdateProject := &authorizationapi.LocalSubjectAccessReview{ diff --git a/test/integration/bootstrap_policy_test.go b/test/integration/bootstrap_policy_test.go index 55015c871b04..c537b2b2dd82 100644 --- a/test/integration/bootstrap_policy_test.go +++ b/test/integration/bootstrap_policy_test.go @@ -112,8 +112,8 @@ func TestBootstrapPolicySelfSubjectAccessReviews(t *testing.T) { localReview: askCanClusterAdminsCreateProject, kubeAuthInterface: valerieKubeClient.Authorization(), kubeNamespace: "openshift", - err: `User "valerie" cannot create localsubjectaccessreviews in project "openshift"`, - kubeErr: `User "valerie" cannot create localsubjectaccessreviews.authorization.k8s.io in project "openshift"`, + err: `User "valerie" cannot create localsubjectaccessreviews in the namespace "openshift"`, + kubeErr: `User "valerie" cannot create localsubjectaccessreviews.authorization.k8s.io in the namespace "openshift"`, }.run(t) } diff --git a/test/integration/oauth_cert_fallback_test.go b/test/integration/oauth_cert_fallback_test.go index 32fdf5cd3a8f..f4f243298686 100644 --- a/test/integration/oauth_cert_fallback_test.go +++ b/test/integration/oauth_cert_fallback_test.go @@ -35,7 +35,7 @@ func TestOAuthCertFallback(t *testing.T) { certUser = "system:admin" unauthorizedError = "the server has asked for the client to provide credentials (get users ~)" - anonymousError = `User "system:anonymous" cannot get users at the cluster scope` + anonymousError = `User "system:anonymous" cannot get users at the cluster scope: User "system:anonymous" cannot get users at the cluster scope (get users ~)` ) // Build master config diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/filters/requestinfo.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/filters/requestinfo.go index 52823f87a390..4c79a8a2844b 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/filters/requestinfo.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/filters/requestinfo.go @@ -26,7 +26,7 @@ import ( ) // WithRequestInfo attaches a RequestInfo to the context. -func WithRequestInfo(handler http.Handler, resolver *request.RequestInfoFactory, requestContextMapper request.RequestContextMapper) http.Handler { +func WithRequestInfo(handler http.Handler, resolver request.RequestInfoResolver, requestContextMapper request.RequestContextMapper) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, ok := requestContextMapper.Get(req) if !ok { diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go index 5c6c9bf75610..df90ccf6a8d0 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/errors.go @@ -44,9 +44,9 @@ func Forbidden(attributes authorizer.Attributes, w http.ResponseWriter, req *htt w.WriteHeader(http.StatusForbidden) if len(reason) == 0 { - fmt.Fprintf(w, "%s", msg) + fmt.Fprintf(w, "%s.", msg) } else { - fmt.Fprintf(w, "%s: %q", msg, reason) + fmt.Fprintf(w, "%s: %s", msg, reason) } } @@ -57,7 +57,7 @@ func forbiddenMessage(attributes authorizer.Attributes) string { } if !attributes.IsResourceRequest() { - return fmt.Sprintf("User %q cannot %s path %q.", username, attributes.GetVerb(), attributes.GetPath()) + return fmt.Sprintf("User %q cannot %s path %q", username, attributes.GetVerb(), attributes.GetPath()) } resource := attributes.GetResource() @@ -69,10 +69,10 @@ func forbiddenMessage(attributes authorizer.Attributes) string { } if ns := attributes.GetNamespace(); len(ns) > 0 { - return fmt.Sprintf("User %q cannot %s %s in the namespace %q.", username, attributes.GetVerb(), resource, ns) + return fmt.Sprintf("User %q cannot %s %s in the namespace %q", username, attributes.GetVerb(), resource, ns) } - return fmt.Sprintf("User %q cannot %s %s at the cluster scope.", username, attributes.GetVerb(), resource) + return fmt.Sprintf("User %q cannot %s %s at the cluster scope", username, attributes.GetVerb(), resource) } // InternalError renders a simple internal error diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go index bbfc547afab8..989517c91782 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go @@ -24,6 +24,10 @@ import ( "k8s.io/apimachinery/pkg/util/sets" ) +type RequestInfoResolver interface { + NewRequestInfo(req *http.Request) (*RequestInfo, error) +} + // RequestInfo holds information parsed from the http.Request type RequestInfo struct { // IsResourceRequest indicates whether or not the request is for an API resource or subresource diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/config.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/config.go index 0d19af7421ab..87a82518a65f 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -137,6 +137,9 @@ type Config struct { // RequestContextMapper maps requests to contexts. Exported so downstream consumers can provider their own mappers // TODO confirm that anyone downstream actually uses this and doesn't just need an accessor RequestContextMapper apirequest.RequestContextMapper + // RequestInfoResolver is used to assign attributes (used by admission and authorization) based on a request URL. + // Use-cases that are like kubelets may need to customize this. + RequestInfoResolver apirequest.RequestInfoResolver // Serializer is required and provides the interface for serializing and converting objects to and from the wire // The default (api.Codecs) usually works fine. Serializer runtime.NegotiatedSerializer @@ -360,6 +363,10 @@ func (c *Config) Complete() completedConfig { c.Authorizer = authorizerunion.New(tokenAuthorizer, c.Authorizer) } + if c.RequestInfoResolver == nil { + c.RequestInfoResolver = NewRequestInfoResolver(c) + } + return completedConfig{c} } @@ -473,7 +480,7 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler { handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true") handler = genericfilters.WithPanicRecovery(handler) handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.RequestContextMapper, c.LongRunningFunc) - handler = genericapifilters.WithRequestInfo(handler, NewRequestInfoResolver(c), c.RequestContextMapper) + handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver, c.RequestContextMapper) handler = apirequest.WithRequestContext(handler, c.RequestContextMapper) return handler } diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go index b8aed64c1f8e..66e508cf59da 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/server/genericapiserver_test.go @@ -107,6 +107,7 @@ func setUp(t *testing.T) (*etcdtesting.EtcdTestServer, Config, *assert.Assertion // }, // } config.SwaggerConfig = DefaultSwaggerConfig() + config.Complete() return etcdServer, *config, assert.New(t) }