-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Impersonate user for ns update during project update #11647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,16 +9,20 @@ import ( | |
"k8s.io/kubernetes/pkg/api/rest" | ||
"k8s.io/kubernetes/pkg/api/unversioned" | ||
kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" | ||
"k8s.io/kubernetes/pkg/client/restclient" | ||
nsregistry "k8s.io/kubernetes/pkg/registry/core/namespace" | ||
"k8s.io/kubernetes/pkg/runtime" | ||
kstorage "k8s.io/kubernetes/pkg/storage" | ||
"k8s.io/kubernetes/pkg/util/validation/field" | ||
"k8s.io/kubernetes/pkg/watch" | ||
|
||
oapi "github.com/openshift/origin/pkg/api" | ||
authclient "github.com/openshift/origin/pkg/auth/client" | ||
authorizationapi "github.com/openshift/origin/pkg/authorization/api" | ||
"github.com/openshift/origin/pkg/authorization/authorizer/scope" | ||
"github.com/openshift/origin/pkg/project/api" | ||
projectapi "github.com/openshift/origin/pkg/project/api" | ||
"github.com/openshift/origin/pkg/project/api/validation" | ||
projectauth "github.com/openshift/origin/pkg/project/auth" | ||
projectcache "github.com/openshift/origin/pkg/project/cache" | ||
projectregistry "github.com/openshift/origin/pkg/project/registry/project" | ||
|
@@ -28,6 +32,8 @@ import ( | |
type REST struct { | ||
// client can modify Kubernetes namespaces | ||
client kcoreclient.NamespaceInterface | ||
// config to build a client that can impersonate users | ||
config restclient.Config | ||
// lister can enumerate project lists that enforce policy | ||
lister projectauth.Lister | ||
// Allows extended behavior during creation, required | ||
|
@@ -40,9 +46,10 @@ type REST struct { | |
} | ||
|
||
// NewREST returns a RESTStorage object that will work against Project resources | ||
func NewREST(client kcoreclient.NamespaceInterface, lister projectauth.Lister, authCache *projectauth.AuthorizationCache, projectCache *projectcache.ProjectCache) *REST { | ||
func NewREST(client kcoreclient.NamespaceInterface, privilegedConfig restclient.Config, lister projectauth.Lister, authCache *projectauth.AuthorizationCache, projectCache *projectcache.ProjectCache) *REST { | ||
return &REST{ | ||
client: client, | ||
config: privilegedConfig, | ||
lister: lister, | ||
createStrategy: projectregistry.Strategy, | ||
updateStrategy: projectregistry.Strategy, | ||
|
@@ -156,6 +163,14 @@ func (s *REST) Update(ctx kapi.Context, name string, objInfo rest.UpdatedObjectI | |
|
||
s.updateStrategy.PrepareForUpdate(ctx, obj, oldObj) | ||
if errs := s.updateStrategy.ValidateUpdate(ctx, obj, oldObj); len(errs) > 0 { | ||
// If the user has the ability to update the namespace directly, they can circumvent most of the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a stupid question, but I was thought that stupid are only those questions that weren't asked ;) Is it possible to have edit rights on project but not on namespace? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. As far as the authorizer is concerned, they are completely separate resources. Almost no one has rights to do anything with namespaces. Also, almost no one can |
||
// project validation code. Thus, as a convenience, if we see that the validation errors are all in | ||
// the project's immutable fields, we try to impersonate the user and update the namespace directly. | ||
if containsOnlyImmutableProjectFieldErrors(errs) { | ||
if namespace, ok := s.tryImpersonatingNamespaceUpdate(project, ctx); ok { | ||
return projectutil.ConvertNamespace(namespace), false, nil | ||
} | ||
} | ||
return nil, false, kerrors.NewInvalid(projectapi.Kind("Project"), project.Name, errs) | ||
} | ||
|
||
|
@@ -167,6 +182,34 @@ func (s *REST) Update(ctx kapi.Context, name string, objInfo rest.UpdatedObjectI | |
return projectutil.ConvertNamespace(namespace), false, nil | ||
} | ||
|
||
func (s *REST) tryImpersonatingNamespaceUpdate(project *api.Project, ctx kapi.Context) (*kapi.Namespace, bool) { | ||
user, ok := kapi.UserFrom(ctx) | ||
if !ok { | ||
return nil, false | ||
} | ||
|
||
impersonatingClient, err := authclient.NewImpersonatingKubernetesClientset(user, s.config) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From an audit standpoint, this will look weird. Normally, you'd get:
Now you're getting:
This might look fishy in the logs and for someone doing security audit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess both you and @pweil- voiced that concern in the main thread. I'm looking from an audit point of view ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The audit flow as I see it now: Success case
Failure case
The audit flow if this PR is merged: Success case 1
Success case 2
Failure case 1
Failure case 2
|
||
if err != nil { | ||
return nil, false | ||
} | ||
|
||
namespace, err := impersonatingClient.Core().Namespaces().Update(projectutil.ConvertProject(project)) | ||
if err != nil { | ||
return nil, false | ||
} | ||
|
||
return namespace, true // There was no error updating the namespace so the user has the rights to do so | ||
} | ||
|
||
func containsOnlyImmutableProjectFieldErrors(errs field.ErrorList) bool { | ||
for _, err := range errs { | ||
if err.Type != field.ErrorTypeInvalid || err.Detail != validation.ImmutableProjectFieldError { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
var _ = rest.Deleter(&REST{}) | ||
|
||
// Delete deletes a Project specified by its name | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import ( | |
"k8s.io/kubernetes/pkg/api/unversioned" | ||
"k8s.io/kubernetes/pkg/auth/user" | ||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" | ||
"k8s.io/kubernetes/pkg/client/restclient" | ||
|
||
oapi "github.com/openshift/origin/pkg/api" | ||
"github.com/openshift/origin/pkg/project/api" | ||
|
@@ -70,7 +71,7 @@ func TestCreateProjectBadObject(t *testing.T) { | |
|
||
func TestCreateInvalidProject(t *testing.T) { | ||
mockClient := &fake.Clientset{} | ||
storage := NewREST(mockClient.Core().Namespaces(), &mockLister{}, nil, nil) | ||
storage := NewREST(mockClient.Core().Namespaces(), restclient.Config{}, &mockLister{}, nil, nil) | ||
_, err := storage.Create(kapi.NewContext(), &api.Project{ | ||
ObjectMeta: kapi.ObjectMeta{ | ||
Annotations: map[string]string{oapi.OpenShiftDisplayName: "h\t\ni"}, | ||
|
@@ -83,7 +84,7 @@ func TestCreateInvalidProject(t *testing.T) { | |
|
||
func TestCreateProjectOK(t *testing.T) { | ||
mockClient := &fake.Clientset{} | ||
storage := NewREST(mockClient.Core().Namespaces(), &mockLister{}, nil, nil) | ||
storage := NewREST(mockClient.Core().Namespaces(), restclient.Config{}, &mockLister{}, nil, nil) | ||
_, err := storage.Create(kapi.NewContext(), &api.Project{ | ||
ObjectMeta: kapi.ObjectMeta{Name: "foo"}, | ||
}) | ||
|
@@ -100,7 +101,7 @@ func TestCreateProjectOK(t *testing.T) { | |
|
||
func TestGetProjectOK(t *testing.T) { | ||
mockClient := fake.NewSimpleClientset(&kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: "foo"}}) | ||
storage := NewREST(mockClient.Core().Namespaces(), &mockLister{}, nil, nil) | ||
storage := NewREST(mockClient.Core().Namespaces(), restclient.Config{}, &mockLister{}, nil, nil) | ||
project, err := storage.Get(kapi.NewContext(), "foo") | ||
if project == nil { | ||
t.Error("Unexpected nil project") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have an explicit test for that new functionality here. The more tests, the better, especially when it comes to security. |
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoa, that's interesting... I don't see that problem locally, but I might be using an older version of goimports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried upgrading my goimports but it kept removing this...