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

net/http: unable to use getpeername to authenticate Unix socket requests #16385

Closed
tiran opened this issue Jul 15, 2016 · 10 comments

Comments

Projects
None yet
8 participants
@tiran
Copy link

commented Jul 15, 2016

Please answer these questions before submitting your issue. Thanks!

  1. What version of Go are you using (go version)?
    go version go1.6.2 linux/amd64
  2. What operating system and processor architecture are you using (go env)?
    Fedora 24 X86_64
  3. What did you do?
    Go doesn't let me access the file descriptor of the connection socket in net.http.HandlerFunc's ServeHTTP() function. While I agree that users should not mess with the file descriptor when it is under control of a HTTP library, it also prevents other use cases. I have written a HTTP server that listens on a Unix domain socket file. Unix sockets are local sockets that have some interesting properties. For example they allow to get the pid, uid, gid and security context (SELinux label) of the peer. This feature can be used for authentication and authorization (GetsockoptUcred #3836, #16374).

With Go 1.6 neither http.ResponseWriter nor http.Request have a public field or func that let me access the connection object, file or file descriptor. The connection is contained within the package internal http.conn struct. I also looked into Go 1.7 RC. Request got a context, but I don't see how that might solve the problem at hand.

Some mailing list posts suggest to use http.Server's ConnState hook to track connections in a map. For IPv4 and IPv6 connection it is possible to map conn.RemoteAddr to conn and then use http.Request.RemoteAddr to retrieve the connection. After all the combination of (src ip, src port, dest ip, dest port) is a unique identifier for TCP over IP. However that is not true for AF_UNIX / SOCK_STREAM. Unix socket's don't implement getpeername(). For a UnixListener the RemoteAddr of a http.Request is always the string '@'. net.Conn and http.Request don't share any other field that could be used as a common identifier.

Eventually I worked around the problem with http.Hijack(). This is a rather ugly solution because I have to roll my own response handling. It also prevents HTTP 1/1 persistent connections. https://github.com/tiran/custodia_goforwarder/blob/master/custodia_goforwarder.go#L95

Please provide a better and simpler way to access the file descriptor.

@bradfitz

This comment has been minimized.

Copy link
Member

commented Jul 15, 2016

There are many issues with exposing the underlying socket, which is why we haven't done so yet. What are you trying to do?

(instead of stating your problem, it seems you're asking for a specific solution?)

@bradfitz bradfitz changed the title Get socket file descriptor in ServeHTTP() net/http: something about Unix socket getpeername? Jul 15, 2016

@tiran

This comment has been minimized.

Copy link
Author

commented Jul 15, 2016

I like to authenticate local Unix connections inside the HTTP request handler ServeHTTP() with https://golang.org/pkg/syscall/#GetsockoptUcred and security context (#16374). syscall.GetsockoptUcred(int(fd), syscall.SOL_SOCKET, syscall.SO_PEERCRED) returns pid, uid and gid of the peer. The operating system ensures that these information are valid and correct.

I'm currently porting some parts of a Python application to Go. Specifically a HTTP forwarder that accepts connections on a Unix socket file, authenticate the request with peer credentials and security context of the connection and then forwards the HTTP request to an upstream service. It was very easy to write the Unix socket listener and HTTP forwarder with Go -- until I hit this road block.

@bradfitz

This comment has been minimized.

Copy link
Member

commented Jul 15, 2016

Did you consider writing a net.Listener implementation?

@tiran

This comment has been minimized.

Copy link
Author

commented Jul 15, 2016

Yes, I also looked into net.Listener and studied the low-level socket code. I couldn't figure out how to get the information into ServeHTTP() without a major rewrite or copy of the standard library. For example I looked into a way to give each UnixConn an unique RemoteAddr. The RemoteAddr is set very deeply inside the socket code (net/sock_posix.go func socket() and netFD.dial()).

I'm not a Go expert. How would a custom net.Listener help?

@quentinmit quentinmit changed the title net/http: something about Unix socket getpeername? net/http: unable to use getpeername to authenticate Unix socket requests Jul 20, 2016

@quentinmit quentinmit added this to the Go1.8Maybe milestone Jul 20, 2016

@mikioh

This comment has been minimized.

Copy link
Contributor

commented Jul 21, 2016

@tiran,

I'm not sure what you want, but perhaps func (c *UnixConn) SyscallConn() (syscall.RawConn, error) from Go 1.9 might be a help if your questions are simply:

  • How to use the net.Listener interface with net/http package?
  • How to scrape out the net.UnixConn?
  • Where is the best place to hook up the L4-L3 identifiers from the stack consists of HTTP over socket over AF_LOCAL protocol?

FWIW, the implementation of socket type and its methods varies between platforms, but in general you need to make a named socket if you want to know its near and far endpoint names even in the case of wildcard names.

@rfc1459

This comment has been minimized.

Copy link

commented Sep 30, 2016

@mikioh: the far end name does not matter (and most likely it's not even bound on Linux), what matters are the credentials of the process on the far end.

The actual issue is that there is no easy way of bubbling up this information from - say - an http.Server.ConnState callback all the way up to an http.HandlerFunc. And by "easy" I mean "short of rewriting net/http or using http.Hijack()".

@rsc

This comment has been minimized.

Copy link
Contributor

commented Oct 20, 2016

If you wrote your own net.Listener wrapper it could return your own net.Conn implementation that embeds the actual net.Conn but overrides the RemoteAddr method, and your implementation could return not just the remote socket name but also the credentials.

So I don't think anything has to go into net/http to make this possible. Of course, it could be made easier, but I think we'd need to figure out how widespread the need for this is.

@rsc rsc modified the milestones: Go1.9, Go1.8Maybe Oct 20, 2016

@bradfitz bradfitz modified the milestones: Go1.10, Go1.9 May 24, 2017

@bradfitz

This comment has been minimized.

Copy link
Member

commented May 24, 2017

This is waiting on a concrete proposal at this point.

@gopherbot

This comment has been minimized.

Copy link

commented Jun 24, 2017

Timed out in state WaitingForInfo. Closing.

(I am just a bot, though. Please speak up if this is a mistake or you have the requested information.)

@gopherbot gopherbot closed this Jun 24, 2017

@odeke-em

This comment has been minimized.

Copy link
Member

commented Jun 24, 2017

Sorry @gopherbot, this is still waiting on a concrete proposal.

@odeke-em odeke-em reopened this Jun 24, 2017

@gopherbot gopherbot closed this Jun 24, 2017

@golang golang locked and limited conversation to collaborators Jun 24, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.