-
Notifications
You must be signed in to change notification settings - Fork 931
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
Avoid race between operation and events listener #12885
Changes from all commits
f26353c
55de107
1085085
72bd241
5017a40
861f757
c3a3f44
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 |
---|---|---|
|
@@ -118,6 +118,16 @@ func (r *ProtocolLXD) CreateContainerFromBackup(args ContainerBackupArgs) (Opera | |
req.Header.Set("Content-Type", "application/octet-stream") | ||
req.Header.Set("X-LXD-pool", args.PoolName) | ||
|
||
// Set up the events listener before making the request so that | ||
// the operation doesn't complete and emit the event before we've begun listening. | ||
var listener *EventListener | ||
if r.supportsAuthentication { | ||
listener, err = r.getEvents(false) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
// Send the request | ||
resp, err := r.DoHTTP(req) | ||
if err != nil { | ||
|
@@ -141,6 +151,7 @@ func (r *ProtocolLXD) CreateContainerFromBackup(args ContainerBackupArgs) (Opera | |
// Setup an Operation wrapper | ||
op := operation{ | ||
Operation: *respOperation, | ||
listener: listener, | ||
r: r, | ||
chActive: make(chan bool), | ||
} | ||
|
@@ -196,8 +207,10 @@ func (r *ProtocolLXD) tryCreateContainer(req api.ContainersPost, urls []string) | |
|
||
rop.targetOp = op | ||
|
||
for _, handler := range rop.handlers { | ||
_, _ = rop.targetOp.AddHandler(handler) | ||
if r.supportsAuthentication { | ||
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 you use |
||
for _, handler := range rop.handlers { | ||
_, _ = rop.targetOp.AddHandler(handler) | ||
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. Should we be ignoring the error returned from AddHandler? 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've seen this sort of thing all over the place wherever we try to set up an events listener with |
||
} | ||
} | ||
|
||
err = rop.targetOp.Wait() | ||
|
@@ -559,8 +572,10 @@ func (r *ProtocolLXD) tryMigrateContainer(source InstanceServer, name string, re | |
|
||
rop.targetOp = op | ||
|
||
for _, handler := range rop.handlers { | ||
_, _ = rop.targetOp.AddHandler(handler) | ||
if source.SupportsAuthentication() { | ||
for _, handler := range rop.handlers { | ||
_, _ = rop.targetOp.AddHandler(handler) | ||
} | ||
} | ||
|
||
err = rop.targetOp.Wait() | ||
|
@@ -1251,8 +1266,10 @@ func (r *ProtocolLXD) tryMigrateContainerSnapshot(source InstanceServer, contain | |
|
||
rop.targetOp = op | ||
|
||
for _, handler := range rop.handlers { | ||
_, _ = rop.targetOp.AddHandler(handler) | ||
if source.SupportsAuthentication() { | ||
for _, handler := range rop.handlers { | ||
_, _ = rop.targetOp.AddHandler(handler) | ||
} | ||
} | ||
|
||
err = rop.targetOp.Wait() | ||
|
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.
@markylaing @masnax will this work with OIDC authenticated clients?
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.
At the moment, I'm guessing no since they won't have TLS certs. @markylaing is there something we can check to see if the client intends to use OIDC auth?
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.
There are two indicators from
ConnectionArgs
:AuthType
andOIDCTokens
. I don't think this will work reliably though,AuthType
may or may not be set (we have a default preference for OIDC over TLS), andOIDCTokens
will not be set if the client has not already authenticated.ProtocolLXD
has anoidcClient
field that will be non-nil if the client has an access token for that remote, so you could potentially use that.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 tests are passing suggesting that the tests arent covering this scenario with the mini-oidc service, can they be updated to check for the regression now and then we can see the fix?
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 don't know much about the mini-oidc tests, but the related change from this PR just determines whether we use long polling or a websocket to track an operation. So we would need a test that explicitly tries to connect to the events websocket to track an operation, and fail if we hit the
/1.0/operations/wait
API.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.
@markylaing Is there a case where the client should be able to create and then track long-lived operations without having
can_view_events
&can_view_projects
? Would it be valid and expected to work if a user tries to create/edit/manage instances/images/storage without also havingcan_view_events
andcan_view_projects
?My expectation was no, that you would need a set of entitlements inclusive of those two for those types of actions, and in that case checking for an intent to authenticate should be enough, we don't necessarily need to know if the connection will be authorized for
/1.0/events
. We already don't for TLS auth, since we just check if certs exist, not if they're actually going to be accepted by the server. So we could let the endpoint just handle the error and correctly return 403 if a client is authorized to create or manage images/instances/storage, but not authorized to use the events API.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, ideally we would like to decouple this and have the entitlements work in isolation. For example, a client with
can_exec
on an instance shouldn't be required to havecan_view_events
for the whole project in order to perform the exec.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 haven't been able to dive into decoupling these yet but my thinking is that adding websocket functionality to
/1.0/operations/{uuid}/wait
OR adding a/1.0/operations/{uuid}/events
would be the way to go. Both can be untrusted and require the websocket secret to function. We would then stop using events in the client except for when specifically usinglxc monitor
for example.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.
Please keep in mind the client and lxc command still need to be able to work with older servers
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'm not sure I follow, that would still behave the same if we infer authentication:
can_exec
, so the request fails with 403 anyway, so it wouldn't matter that we set up a bad websocket connection.Seems to me that's true especially if we want to have entitlements work in isolation. That would mean that whenever a client is authorized for a particular action that can involve a websocket, it must necessarily be authorized to start a websocket on that action by the fact that its authorized for the action itself. As you said, we don't need
can_view_events
to view events in that case.Can you give an example of a situation where we might incorrectly infer that we should start a websocket for a particular action, that action is in fact authorized, but the websocket connection is not?