-
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
Conversation
} | ||
|
||
return projectutil.ConvertNamespace(namespace), false, nil | ||
} | ||
|
||
func (s *REST) getImpersonatingClientFor(user user.Info) kclient.NamespaceInterface { |
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.
get a client config and do this:
impersonatingConfig := a.privilegedRESTClientConfig
oldWrapTransport := impersonatingConfig.WrapTransport
impersonatingConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return authenticationclient.NewImpersonatingRoundTripper(attributes.GetUserInfo(), oldWrapTransport(rt))
}
or make a helper down in the pkg/auth/client
package.
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.
that reconstructs from config on every request, potentially down to the point of re-reading and re-parsing CA and cert files... needs to be split up to only build the base transport once
@@ -154,19 +158,38 @@ func (s *REST) Update(ctx kapi.Context, name string, objInfo rest.UpdatedObjectI | |||
return nil, false, fmt.Errorf("not a project: %#v", obj) | |||
} | |||
|
|||
s.updateStrategy.PrepareForUpdate(ctx, obj, oldObj) | |||
if errs := s.updateStrategy.ValidateUpdate(ctx, obj, oldObj); len(errs) > 0 { |
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.
If you get a validation error and it is in metadata
, you can try the impersonating update on the namespace.
Otherwise, you can escalate using your privileged client.
b7a64c3
to
81c81d0
Compare
[test] |
97880d7
to
a80f4d4
Compare
re[test] Flake #11661 |
a80f4d4
to
c1641a0
Compare
test/cmd/projects.sh
Outdated
# test that standard user still cannot edit immutable fields | ||
os::cmd::expect_failure_and_text 'oc annotate project nstestproj key1=val1 --overwrite' 'The Project "nstestproj" is invalid: metadata.annotations\[key1\]: Invalid value: "val1": field is immutable, try updating the namespace' | ||
os::cmd::expect_failure_and_text 'oc annotate namespace nstestproj key2=val2 --overwrite' 'Error from server: User "nstest" cannot "patch" "namespaces" with name "nstestproj" in project "nstestproj"' | ||
# this will now be successful because there is no diff between the old and new project |
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 would be curious to know if you guys consider this behavior valid (seems a bit confusing).
5d97d57
to
db3ccba
Compare
test/cmd/projects.sh
Outdated
os::cmd::expect_success_and_text 'oc login -u nstest -p nstest' 'Login successful.' | ||
os::cmd::expect_failure_and_text 'oc label project nstestproj keyA=valA --overwrite' 'The Project "nstestproj" is invalid: metadata.labels\[keyA\]: Invalid value: "valA": field is immutable, try updating the namespace' | ||
os::cmd::expect_failure_and_text 'oc label namespace nstestproj keyB=valB --overwrite' 'Error from server: User "nstest" cannot "patch" "namespaces" with name "nstestproj" in project "nstestproj"' | ||
os::cmd::expect_success_and_text 'oc label project nstestproj keyC=valC --overwrite' 'project "nstestproj" not labeled' |
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.
oc label
(unlike oc annotate
) checks to see if there is a diff between the two objects when printing the success message.
bump |
pkg/auth/client/impersonate.go
Outdated
@@ -48,3 +51,21 @@ func cloneRequest(r *http.Request) *http.Request { | |||
} | |||
return r2 | |||
} | |||
|
|||
func buildImpersonatingConfig(user user.Info, impersonatingConfig *restclient.Config) *restclient.Config { |
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.
Why a pointer? It makes reasoning about whether you're wrapping the same transport deeper and deeper over time harder since I have to find the callers.
|
||
// PrivilegedLoopbackOpenShiftClientConfig is the client configuration used to call OpenShift APIs from system components | ||
// To apply different access control to a system component, create a client config specifically for that component. | ||
PrivilegedLoopbackOpenShiftClientConfig restclient.Config |
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.
Is this one actually different? We no longer allow the proxy to kube configuration.
if err != nil { | ||
return nil, false, err | ||
} | ||
|
||
return projectutil.ConvertNamespace(namespace), false, nil | ||
} | ||
|
||
func (s *REST) tryImpersonatingNamespaceUpdateOnMetadataError(namespace *kapi.Namespace, ctx kapi.Context, errs field.ErrorList) (*kapi.Namespace, error) { | ||
// If the error is in the metadata's annotations or labels, we try to impersonate the user and update the namespace | ||
if containsAnnotationOrLabelMetadataError(errs) { |
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.
This decision should be made before the method call. Maybe a switch/case after checking the strategy.
rebase, update your description, decide if its still a WIP |
@enj ping :) |
Signed-off-by: Monis Khan <[email protected]>
db3ccba
to
a7aae0e
Compare
Evaluated for origin test up to a7aae0e |
@deads2k Ready for another review. I have reservations about adding something like this. Is the slight extra convenience really worth all this complexity? Very few people have access to mutate namespaces, so I feel like they should be knowledgeable enough to know the difference. My biggest concern is that any mistake on my part here could break multitenancy. cc @liggitt @mfojtik @soltysh @smarterclayton @pweil- for thoughts. |
Is the fundamental question here whether or not the project API is supposed to be a proxy to the (highly privileged) namespace API? I would argue no but agree that the project vs namespace is confusing. Is there a better way to display the error message if the user has access to the namespace API? |
We literally call the file
I agree that it is confusing but I am not sure how to make it less so.
We could certainly do the check, but it would be an expensive loopback REST call. |
continuous-integration/openshift-jenkins/test FAILURE (https://ci.openshift.redhat.com/jenkins/job/test_pull_request_origin/606/) (Base Commit: 4b16943) |
Flake #13662 |
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.
Unit test please, all the rest looks ok. Just a few questions, mostly.
@@ -16,7 +16,7 @@ import ( | |||
restful "github.com/emicklei/go-restful" | |||
"github.com/golang/glog" | |||
"github.com/prometheus/client_golang/prometheus" | |||
"gopkg.in/natefinch/lumberjack.v2" | |||
lumberjack "gopkg.in/natefinch/lumberjack.v2" // goimports will remove this if the alias is not set |
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...
@@ -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 comment
The 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 comment
The 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 create
projects. This is how the projectrequest
flow works (you say I want a project, the system makes a project under specific constraints, and then the underlying namespace is created).
return nil, false | ||
} | ||
|
||
impersonatingClient, err := authclient.NewImpersonatingKubernetesClientset(user, s.config) |
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.
From an audit standpoint, this will look weird. Normally, you'd get:
- request to update project
- request to update namespace
Now you're getting:
- request to update project
- request to update project as someone else
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
The audit flow as I see it now:
Success case
- Request by user to update project
- No validation error
- Request by loopback client to update namespace
- Success
Failure case
- Request by user to update project
- Validation error
- Failure
The audit flow if this PR is merged:
Success case 1
- Request by user to update project
- No validation error
- Request by loopback client to update namespace
- Success
Success case 2
- Request by user to update project
- Immutable field error
- Request by loopback client that is impersonating the user to update namespace
- User can update namespaces
- Success
Failure case 1
- Request by user to update project
- Non immutable field error
- Failure
Failure case 2
- Request by user to update project
- Immutable field error
- Request by loopback client that is impersonating the user to update namespace
- User cannot update namespaces
- Failure
@@ -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 comment
The 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.
Rather than spending a bunch of time on this - I'd rather let users be able to edit a set of "safe" annotations on their project. I don't really care that admins have to use namespace to edit their namespace. Project is a limited permission view, with specific constraints. Why add complexity? |
I wish I'd seen this sooner. I would really have just preferred to add a better message. |
Adds too much complexity for no real gains. |
Looked pretty simple to me. Oh well. |
https://bugzilla.redhat.com/show_bug.cgi?id=1386401
Signed-off-by: Monis Khan [email protected]
cc @deads2k