Skip to content

Instantly share code, notes, and snippets.

@joncfoo
Last active November 7, 2020 16:05
Show Gist options
  • Save joncfoo/acdd780e8347fd55273daa255039ba75 to your computer and use it in GitHub Desktop.
Save joncfoo/acdd780e8347fd55273daa255039ba75 to your computer and use it in GitHub Desktop.
Peer Credentials from Unix Domain Socket
// Unix Domain Socket Peer Credentials
//
// This program demonstrates setting up a HTTP server over a Unix Domain Socket
// and subsequently obtaining connecting clients credentials. The credentials
// contain the User ID, Group ID, and Process ID of the incoming connection (as
// connections are made by some local system user as opposed to a network
// connection).
//
// This has interesting use cases around local process security. For example,
// a server listening on a Unix Domain Socket can choose to allow or reject
// incoming connections based on any combination of User ID, Group ID, or
// Process ID. Couple this technique with process privilege separation to
// build secure programs.
//
// Note: error checking and other best practices have been omitted for brevity.
//
// Overview:
// 1. Create a unix domain socket over which we can serve HTTP requests
// 2. As connections to the server are created, grab the credentials associated
// with the connection.
// 3. Make use of the credentials in a HTTP handler
//
// References:
//
// Using SO_PEERCRED in Go
// - https://blog.jbowen.dev/2019/09/using-so_peercred-in-go/
// Dropping privileges and passing a file descriptor
// - https://gist.github.com/jsimonetti/e31dced5875903d65677e66e103168cf
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"golang.org/x/sys/unix"
)
func main() {
// # Step 1
// Set up a unix domain socket, a special file on disk over which a server
// and clients can communicate.
socketFile := "example.socket"
os.Remove(socketFile)
listener, _ := net.Listen("unix", socketFile)
// Context key for credentials we will obtain from incoming connections.
var credentialsContextKey = struct{}{}
// # Step 2
// Create the HTTP server
server := &http.Server{
// `ConnContext` allows us to inspect the incoming client connection
// and modify the `Context` which is subsequently made available to all
// HTTP handlers on the server for this connection.
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
// Obtain the file abstraction from the incoming connection.
file, _ := c.(*net.UnixConn).File()
// Obtain the Unix Credentials from the underlying file descriptor.
// > The credentials are of type `*unix.Ucred`.
credentials, _ := unix.GetsockoptUcred(int(file.Fd()), unix.SOL_SOCKET, unix.SO_PEERCRED)
// Return a modified context that contains the credentials we obtained
return context.WithValue(ctx, credentialsContextKey, credentials)
},
}
// # Step 3
// Attach a simple HTTP handler where we can make use of the incoming
// connection's credentials.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Retrieve the credentials from the request context
credentials := r.Context().Value(credentialsContextKey).(*unix.Ucred)
// Return the credentials over the HTTP response.
fmt.Fprintf(w, "Connection credentials: %+v\n", credentials)
})
// Note: the above can be abstracted into middleware that chooses to
// enforce security conditions.
// Start the HTTP server
server.Serve(listener)
}
// Test the program.
//
// In one terminal, execute `go run ./main.go`.
// In another terminal, execute `curl --unix-socket example.socket http://socket/`.
//
// You should see output similar to the following:
// Connection credentials: &{Pid:9025 Uid:1000 Gid:1000}
//
// Try executing the `curl` command as the root user.
@joncfoo
Copy link
Author

joncfoo commented Nov 7, 2020

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment