-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(errors): Add package to control errors returned by the services (#…
…434) To improve user experience, we need to remove some chained errors displayed when something fails during authentication. To keep the broker messages intact, we now rely on a separate error type and gRPC interceptors to remove and format the final error messages that are displayed. UDENG-3420 UDENG-3419 UDENG-3412 UDENG-3418
- Loading branch information
Showing
36 changed files
with
256 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package errmessages | ||
|
||
// ErrToDisplay defines an error that needs to be sent unaltered to the client. | ||
type ErrToDisplay struct { | ||
error | ||
} | ||
|
||
// NewErrorToDisplay returns a new ErrorToDisplay. | ||
func NewErrorToDisplay(err error) error { | ||
return ErrToDisplay{err} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package errmessages | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
func TestRedactErrorInterceptor(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := map[string]struct { | ||
inputError error | ||
|
||
wantMessage string | ||
}{ | ||
"Trim input down to ErrToDisplay": { | ||
inputError: fmt.Errorf("Error to be redacted: %w", ErrToDisplay{errors.New("Error to be shown")}), | ||
wantMessage: "Error to be shown", | ||
}, | ||
"Return original error": { | ||
inputError: errors.New("Not a redacted error"), | ||
wantMessage: "Not a redacted error", | ||
}, | ||
} | ||
for name, tc := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
_, err := RedactErrorInterceptor(context.TODO(), testRequest{tc.inputError}, nil, testHandler) | ||
require.Error(t, err, "RedactErrorInterceptor should return an error") | ||
require.Equal(t, tc.wantMessage, err.Error(), "RedactErrorInterceptor returned unexpected error message") | ||
}) | ||
} | ||
} | ||
|
||
func TestFormatErrorMessage(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := map[string]struct { | ||
inputError error | ||
|
||
wantMessage string | ||
}{ | ||
"Non-gRPC error is left untouched": { | ||
inputError: errors.New("Non-gRPC error"), | ||
wantMessage: "Non-gRPC error", | ||
}, | ||
"Unrecognized error is left untouched": { | ||
inputError: status.Error(100, "Unrecognized error"), | ||
wantMessage: "error Code(100) from server: Unrecognized error", | ||
}, | ||
"Code Canceled is left untouched": { | ||
inputError: status.Error(codes.Canceled, "Canceled error"), | ||
wantMessage: "rpc error: code = Canceled desc = Canceled error", | ||
}, | ||
|
||
"Parse code Unavailable": { | ||
inputError: status.Error(codes.Unavailable, "Unavailable error"), | ||
wantMessage: "couldn't connect to authd daemon: Unavailable error", | ||
}, | ||
"Parse code DeadlineExceeded": { | ||
inputError: status.Error(codes.DeadlineExceeded, "DeadlineExceeded error"), | ||
wantMessage: "service took too long to respond. Disconnecting client", | ||
}, | ||
"Parse code Unknown": { | ||
inputError: status.Error(codes.Unknown, "Unknown error"), | ||
wantMessage: "Unknown error", | ||
}, | ||
} | ||
for name, tc := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
err := FormatErrorMessage(context.TODO(), "", testRequest{tc.inputError}, nil, nil, testInvoker) | ||
require.Error(t, err, "FormatErrorMessage should return an error") | ||
require.Equal(t, tc.wantMessage, err.Error(), "FormatErrorMessage returned unexpected error message") | ||
}) | ||
} | ||
} | ||
|
||
type testRequest struct { | ||
err error | ||
} | ||
|
||
func testHandler(ctx context.Context, req any) (any, error) { | ||
//nolint:forcetypeassert // This is only used in the tests and we know the type. | ||
return nil, req.(testRequest).err | ||
} | ||
|
||
func testInvoker(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, opts ...grpc.CallOption) error { | ||
//nolint:forcetypeassert // This is only used in the tests and we know the type. | ||
return req.(testRequest).err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Package errmessages formats the error messages that are sent to the client. | ||
package errmessages | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"log/slog" | ||
|
||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
// RedactErrorInterceptor redacts some of the attached errors before sending it to the client. | ||
// | ||
// It unwraps the error up to the first ErrToDisplay and sends it to the client. If none is found, it sends the original error. | ||
func RedactErrorInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { | ||
m, err := handler(ctx, req) | ||
if err != nil { | ||
slog.Warn(err.Error()) | ||
var redactedError ErrToDisplay | ||
if !errors.As(err, &redactedError) { | ||
return m, err | ||
} | ||
return m, redactedError | ||
} | ||
return m, nil | ||
} | ||
|
||
// FormatErrorMessage formats the error message received by the client to avoid printing useless information. | ||
// | ||
// It converts the gRPC error to a more human-readable error with a better message. | ||
func FormatErrorMessage(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { | ||
err := invoker(ctx, method, req, reply, cc, opts...) | ||
if err == nil { | ||
return nil | ||
} | ||
st, grpcErr := status.FromError(err) | ||
if !grpcErr { | ||
return err | ||
} | ||
|
||
switch st.Code() { | ||
// no daemon | ||
case codes.Unavailable: | ||
err = fmt.Errorf("couldn't connect to authd daemon: %v", st.Message()) | ||
// timeout | ||
case codes.DeadlineExceeded: | ||
err = fmt.Errorf("service took too long to respond. Disconnecting client") | ||
// regular error without annotation | ||
case codes.Unknown: | ||
err = errors.New(st.Message()) | ||
// likely means that IsAuthenticated got cancelled, so we need to keep the error intact | ||
case codes.Canceled: | ||
break | ||
// grpc error, just format it | ||
default: | ||
err = fmt.Errorf("error %s from server: %v", st.Code(), st.Message()) | ||
} | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...m/testdata/TestIsAuthenticated/golden/error_on_empty_data_even_if_granted/IsAuthenticated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FIRST CALL: | ||
access: | ||
msg: | ||
err: rpc error: code = Unknown desc = can't check authentication: missing key "userinfo" in returned message, got: {} | ||
err: can't check authentication: missing key "userinfo" in returned message, got: {} |
2 changes: 1 addition & 1 deletion
2
...sAuthenticated/golden/error_on_updating_local_groups_with_unexisting_file/IsAuthenticated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FIRST CALL: | ||
access: | ||
msg: | ||
err: rpc error: code = Unknown desc = can't check authentication: failed to update user "TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open testdata/TestIsAuthenticated/does_not_exists.group: no such file or directory | ||
err: can't check authentication: failed to update user "TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open testdata/TestIsAuthenticated/does_not_exists.group: no such file or directory |
2 changes: 1 addition & 1 deletion
2
...ervices/pam/testdata/TestIsAuthenticated/golden/error_when_authenticating/IsAuthenticated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FIRST CALL: | ||
access: | ||
msg: | ||
err: rpc error: code = Unknown desc = can't check authentication: broker "BrokerMock": IsAuthenticated errored out | ||
err: broker "BrokerMock": IsAuthenticated errored out |
2 changes: 1 addition & 1 deletion
2
...tdata/TestIsAuthenticated/golden/error_when_broker_returns_invalid_access/IsAuthenticated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FIRST CALL: | ||
access: | ||
msg: | ||
err: rpc error: code = Unknown desc = can't check authentication: invalid access authentication key: invalid | ||
err: can't check authentication: invalid access authentication key: invalid |
2 changes: 1 addition & 1 deletion
2
...estdata/TestIsAuthenticated/golden/error_when_broker_returns_invalid_data/IsAuthenticated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
FIRST CALL: | ||
access: | ||
msg: | ||
err: rpc error: code = Unknown desc = can't check authentication: response returned by the broker is not a valid json: invalid character 'i' looking for beginning of value | ||
err: can't check authentication: response returned by the broker is not a valid json: invalid character 'i' looking for beginning of value | ||
Broker returned: invalid |
2 changes: 1 addition & 1 deletion
2
...ata/TestIsAuthenticated/golden/error_when_broker_returns_invalid_userinfo/IsAuthenticated
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
FIRST CALL: | ||
access: | ||
msg: | ||
err: rpc error: code = Unknown desc = can't check authentication: message is not JSON formatted: json: cannot unmarshal string into Go value of type brokers.userInfo | ||
err: can't check authentication: message is not JSON formatted: json: cannot unmarshal string into Go value of type brokers.userInfo |
Oops, something went wrong.