-
-
Notifications
You must be signed in to change notification settings - Fork 536
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
[management] improve zitadel idp error response detail by decoding errors #2634
Conversation
c29dac6
to
1176f2a
Compare
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.
Could you also include handling for the Zitadel error when requesting JWT token?
netbird/management/server/idp/zitadel.go
Lines 178 to 180 in 765aba2
if resp.StatusCode != http.StatusOK { | |
return nil, fmt.Errorf("unable to get zitadel token, statusCode %d", resp.StatusCode) | |
} |
yeah, i think since the only thing the method references internally is the helper, and they're two different structs it makes more sense to pull the parser out as a standalone utility function within the package and pass the bound helper of zm/zc + the io.readcloser of resp.Body. I have the changes laid out like that locally here, but lmk if you'd like to see it go in that direction vs. something else. |
Yes, you can extract the parser as a standalone function and initialize the helper within that function, as it's an instance of |
cb3815c
to
a54e173
Compare
ah, have some tests to clean up to expect a zitadel message in the output. sorry didnt check first. |
I ran the tests and found an issue when the JWT request fails. It seems they're using a different error response format
Error format: To make it generic lets return the // readZitadelError parses errors returned by the zitadel APIs from a response.
func readZitadelError(body io.ReadCloser) error {
bodyBytes, err := io.ReadAll(body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
return errors.New(string(bodyBytes))
} Also, the Zitadel tests are failing. Could you take a look, or would you like some help with that? |
yeah, I was just going to follow up to mention that it's a different error format with I'm on board with just stringifying the body and returning that. the only downside I think is that with the other error response types you get extra data that isn't really important to the error, like encoded type info about the message field being a string. |
If there's a possibility of extra fields in the error response (which I couldn't reproduce), we can extend the |
before pusing it up, how about this: // readZitadelError parses errors returned by the zitadel APIs from a response.
func readZitadelError(body io.ReadCloser) error {
bodyBytes, err := io.ReadAll(body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
helper := JsonParser{}
var target map[string]interface{}
err = helper.Unmarshal(bodyBytes, &target)
if err != nil {
return fmt.Errorf("error unparsable body: %s", string(bodyBytes))
}
// ensure keys are ordered for consistent logging behaviour.
errorKeys := make([]string, 0, len(target))
for k := range target {
errorKeys = append(errorKeys, k)
}
slices.Sort(errorKeys)
var errsOut []string
for _, k := range errorKeys {
if _, isEmbedded := target[k].(map[string]interface{}); isEmbedded {
continue
}
errsOut = append(errsOut, fmt.Sprintf("[%v: %v]", k, target[k]))
}
if len(errsOut) == 0 {
return fmt.Errorf("[data missing]")
}
return fmt.Errorf(strings.Join(errsOut, " "))
}
omits any extra embedded fields, gets rid of the extra data structures and returns the top level elements that we care about. I updated the test cases like this to demonstrate: requestJWTTokenTestCase2 := requestJWTTokenTest{
name: "Request Bad Status Code",
inputCode: 400,
inputRespBody: "{\"error\": \"invalid_scope\", \"error_description\":\"openid missing\"}",
helper: JsonParser{},
expectedFuncExitErrDiff: fmt.Errorf("unable to get zitadel token, statusCode 400, zitadel: [error: invalid_scope] [error_description: openid missing]"),
expectedToken: "",
} |
This looks good. Just a minor change, remove the brackets around the error message and we’ll be good to go. |
more generically parse the error returned by zitadel.
a54e173
to
e3cf807
Compare
f13adf6
to
9f697e8
Compare
Quality Gate passedIssues Measures |
Describe your changes
Adds a zitadelErrorResponse struct to decode errors returned from zitadel rather than just returning a status code, so more information can be learned about potential configuration issues. Helped to diagnose #2616
I have some other small changes and improvements to compatibility later, but this was a pretty clear cut and standalone improvement.
Checklist