Skip to content
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

Do not replace AccessDenied error messages with generic "access denied" #39558

Merged
merged 5 commits into from Mar 21, 2024

Conversation

gzdunek
Copy link
Contributor

@gzdunek gzdunek commented Mar 19, 2024

Closes #32550

After merging #39229 we no longer compare the error message on the Electron side to check if the error is retryable. Because of that, we don't need to convert all access denied errors to generic "access denied".
I went through all the remote calls in tshd, I didn't see any place without AddMetadataToRetryableError, so showing the error modal should work as before.

I also removed isAccessDeniedError that was doing the check on error message (I cleaned up the other functions as well).

Changelog: Teleport Connect now shows specific error messages instead of generic "access denied"

@gzdunek gzdunek added teleport-connect Issues related to Teleport Connect. backport/branch/v15 labels Mar 19, 2024
@gzdunek gzdunek removed the request for review from ibeckermayer March 19, 2024 12:08
@ravicious ravicious self-requested a review March 20, 2024 13:27
Copy link
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic!

Comment on lines 259 to 260
* It doesn't contain UNAUTHENTICATED and ABORTED values, which could be
* mistakenly used as PERMISSION_DENIED and CANCELLED.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the thought, but I'm not totally sold on hiding ABORTED. AFAIK, CANCELLED is typically used for cancellation by the client.

ABORTED is meant to be used on the server side:

Service implementors can use the following guidelines to decide between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: (a) Use UNAVAILABLE if the client can retry just the failing call. (b) Use ABORTED if the client should retry at a higher level (e.g., when a client-specified test-and-set fails, indicating the client should restart a read-modify-write sequence). (c) Use FAILED_PRECONDITION if the client should not retry until the system state has been explicitly fixed.

We actually do use ABORTED and it's when tshd sends PromptMFA to the Electron app. If the user closes the modal, the Electron app responds with ABORTED (see below).

Overall I feel like we should keep ABORTED to avoid the inverse – encourage people to use CANCELLED where ABORTED would be better suited.

In the context of a client ABORTED seems important. A client will have little need to check for CANCELLED since cancellation will typically be done on client side, by e.g. aborting a signal. On the other hand, there might be scenarios where a client needs to know if the server ABORTED a call.

if (hasCanceledModal) {
// Throwing an object here instead of an error to future-proof this code in case we need to
// return more than just the error name and message.
// Refer to processEvent in the preload part of tshd events service for more details.
throw {
isCrossContextError: true,
name: 'AbortError',
message: 'MFA modal closed by user',
};
}

// TODO(ravicious): This is just an example of how cross-context errors can be signalled.
// A more elaborate implementation should use a TypeScript assertion function
// (isCrossContextError) and them somehow automatically build common gRPC status errors.
// https://github.com/gravitational/teleport.e/issues/853
// https://github.com/gravitational/teleport/issues/30753
if (error['isCrossContextError'] && error['name'] === 'AbortError') {
responseErr = new grpc.StatusBuilder()
.withCode(grpc.status.ABORTED)
.withDetails(error['message']);
}

// If the user closes the modal in the Electron app, we need to be able to cancel the other
// goroutine as well so that we stop waiting for the hardware key tap.
if err != nil && status.Code(err) == codes.Aborted {
cancel(err)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I will also add UNAUTHENTICATED, I think there is no justification for omitting a single value.

| 'DATA_LOSS';

/**
* Checks if the given value is a `TshdRpcError`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a line explaining that it's meant to be used to check errors received from a gRPC client that crosses context bridge.

If someone looks at this function and they're not aware of how gRPC clients and context bridge interact together, it might be tempting to use this function for other purposes, e.g. somewhere within tshd events backend implementation. But that would just lead someone to a dead end.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I rephrased the messaged a bit:

It's meant to be used to check errors received from a gRPC client that was produced with cloneClient().

It's because we clone the gRPC client in the main process too (so we can check for isResolvableWithRelogin if we need to).

@@ -238,7 +238,7 @@ function AgentSetup() {
await ctx.connectMyComputerService.createRole(rootClusterUri);
certsReloaded = response.certsReloaded;
} catch (error) {
if (isAccessDeniedError(error)) {
if (isTshdRpcError(error, 'PERMISSION_DENIED')) {
Copy link
Member

@ravicious ravicious Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to your work on gRPC clients and errors we can fix a bug here.

If we just catch all PERMISSION_DENIED errors, we're going to catch retryable PERMISSION_DENIED errors as well and retryWithRelogin won't be triggered.

              if (
                isTshdRpcError(error, 'PERMISSION_DENIED') &&
                !error.isResolvableWithRelogin
              ) {
                throw new Error(
                  `Cannot set up the role: ${error.message}. Contact your administrator for permissions to manage users and roles.`
                );
              }
cmc-access-denied

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed!

@gzdunek gzdunek added this pull request to the merge queue Mar 21, 2024
Merged via the queue into master with commit 48c05b6 Mar 21, 2024
39 checks passed
@gzdunek gzdunek deleted the gzdunek/show-access-denied-errors branch March 21, 2024 16:19
@public-teleport-github-review-bot

@gzdunek See the table below for backport results.

Branch Result
branch/v15 Failed

gzdunek added a commit that referenced this pull request Mar 22, 2024
…ied" (#39558)

* Do not replace `AccessDenied` error messages with generic "access denied"

* Replace `error.code` checks with `isTshdRpcError(error, code)`

* Add `ABORTED` and `UNAUTHENTICATED` codes

* Improve `isTshdRpcError` documentation

* Do not catch `retryWithRelogin` errors in `Setup.tsx`

(cherry picked from commit 48c05b6)
github-merge-queue bot pushed a commit that referenced this pull request Mar 25, 2024
…ss denied" (#39720)

* Clone the tshd gRPC client to allow inspecting errors (#39229)

* Add functions to clone every gRPC method, so they can be passed over the context bridge

* Replace `@grpc/grpc-js` interceptors with `@protobuf-ts/runtime-rpc` ones

* Replace string check with check on metadata, add missing `AddMetadataToRetryableError`

* Add `pin` to sensitive properties

It's needed because now we also log a passwordless login stream call.

* Add `cloneClient` method to clone the entire gRPC client

* Switch tests to `TshdRetryableError`

* Improve comments

* Move `cloneClient` and `cloneAbortSignal` to the top of the file

* Simplify condition

* Lowercase logs

* Check if the error is `RpcError` using `instanceof`

* Log error object instead of `[object Object]`

* Do not export call specific types

* Wrap gRPC calls in `createClient` to allow passing the errors over the context bridge (#39230)

* Regenerate protos with the `protobuf-ts` client

* Enable `strictBindCallApply` so `.bind()` results have correct types (instead of `any`)

This is needed for wrapping calls in `createClient`.

* Use `protobuf-ts` client instead of `grpc-js` one

* Convert `createClient` to use `protobuf-ts` response style, clone each call

* Switch callsites to `cloneAbortSignal`

* Replace `error.message` checks with a proper check on the error status code

* Clone the entire client instead of each method separately

* Correct the error `cause` in `ResourceSearchError`

* Remove `params.sortBy` defaults, always set `startKey` to string

* Use a simpler type for `reportUsageEvent`

* Fix interceptor test

* Ignore TS error in `highbar.ts`

(cherry picked from commit 784d7ac)

* Do not replace `AccessDenied` error messages with generic "access denied" (#39558)

* Do not replace `AccessDenied` error messages with generic "access denied"

* Replace `error.code` checks with `isTshdRpcError(error, code)`

* Add `ABORTED` and `UNAUTHENTICATED` codes

* Improve `isTshdRpcError` documentation

* Do not catch `retryWithRelogin` errors in `Setup.tsx`

(cherry picked from commit 48c05b6)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Teleport Connect should not replace AccessDenied error messages with generic "access denied"
4 participants