The HTTP error code returned by lnd using REST API is not correct. It returns 500 Internal server error instead of 4xx. This is confusing when debugging.
lnd 0.5.1uname -a on *Nix) Linux <redacted> 3.16.0-4-amd64 #1 SMP Debian 3.16.51-3 (2017-12-13) x86_64 GNU/Linuxrestlisten=127.0.0.1:9737wget --content-on-error --no-check-certificate -O - https://127.0.0.1:9737/v1/getinfoError 403 Forbidden or other reasonable 4xx error is returned.
500 Internal server error is returned
Ironically, since error 500 means there's something broken with the server, it's strictly true (server sending bad status code), but it doesn't help at all.
Just adding some info here: I think a 401 would be the most appropriate:
401 Unauthorized
Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response.
Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#Client_error_responses
More details: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
The specific difference to 403 is that a 401 means "try again with credentials", while 403 means "I got your credentials but they don't allow you to access the requested resource".
So when no macaroons are sent, the server should respond with "try again with credentials" - i.e. 401.
I was thinking about it but many browsers automatically assume basic authentication (but maybe only with some additional header?), so I'm unsure if it's actually suitable.
I think that depends on the WWW-Authenticate header, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate.
There's a link to a list of alternative standard authentication schemes (alternatives to Basic), but I'm not sure if macaroons fit to any of those: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
It seems to be okay to define your own scheme, so maybe Macaroon can be used? See https://tools.ietf.org/html/draft-ietf-httpbis-p7-auth-19#section-4.4.
In that case, I agree for it to return 401. It should be made sure to return 403 in case of invalid macaroon.
Ah this really got me today 馃槗
I might give this a shot on the weekend, but I might need some guidance for my first code contribution.
Before I create a PR I'd love to get feedback from someone who knows the LND code base regarding if I'm on the right track:
codes.Unauthenticated maps to http.StatusUnauthorized, codes.PermissionDenied to http.StatusForbidden, only unmapped errors are http.StatusInternalServerErrorDigging deeper:
getinfo RPC with its gateway info is defined heregetinfo error is handled here (which is generated code)WWW-Authenticate header as mentioned in my previous commentValidateMacaroon() is called hereError() method)return fmt.Errorf("expected 1 macaroon, got %d", len(md["macaroon"]))hex.DecodeString() the returned error from the stdlib is returnedgrpc.UnaryInterceptorSo the solution seems to be to let ValidateMacaroon() return a Go error via gRPC's status.Error() (see GoDoc) with using the proper gRPC error code (either PermissionDenied or Unauthenticated according to above comments) (see GoDoc), as well as probably customizing the gateway response for the proper WWW-Authenticate header.
I looked for tests that would need to be extended and found TestValidateMacaroon in service_test.go (see here). I didn't find any tests for the gateway yet though.
P.S.: I didn't debug into the code yet, I just went through the code in an editor.
@philippgille Good detective work!
From looking at how grpc intereptors are used, what you might need is for the error from macaroon validation to conform to the GRPCStatus interface: https://github.com/grpc/grpc-go/blob/master/status/status.go#L126
@halseth: Thanks for letting me know that I'm on the right track!
what you might need is for the error from macaroon validation to conform to the GRPCStatus interface
Yes, my proposal was to use gRPC's status.Error(...):
So the solution seems to be to let ValidateMacaroon() return a Go error via gRPC's status.Error() (see GoDoc)
And according to the GoDoc of status.FromError() that you linked to:
FromError returns a Status representing err if it was produced from this package
This should be exactly what we need.
I'll work on a PR then :)