lib/http: disable automatic authentication skipping for unix sockets

Disabling the authentication for unix sockets makes it impossible to
use `rclone serve` behind a proxy that that communicates with rclone
via a unix socket.

Re-enabling the authentication should not have any effect on most
users of unix sockets as they do not set authentication up with a unix
socket anyway.
This commit is contained in:
Moises Lima 2024-10-10 14:42:42 -03:00 committed by Nick Craig-Wood
parent 175aa07cdd
commit 29fd894189
4 changed files with 80 additions and 51 deletions

View File

@ -36,12 +36,6 @@ func IsAuthenticated(r *http.Request) bool {
return false return false
} }
// IsUnixSocket checks if the request was received on a unix socket, used to skip auth & CORS
func IsUnixSocket(r *http.Request) bool {
v, _ := r.Context().Value(ctxKeyUnixSock).(bool)
return v
}
// PublicURL returns the URL defined in NewBaseContext, used for logging & CORS // PublicURL returns the URL defined in NewBaseContext, used for logging & CORS
func PublicURL(r *http.Request) string { func PublicURL(r *http.Request) string {
v, _ := r.Context().Value(ctxKeyPublicURL).(string) v, _ := r.Context().Value(ctxKeyPublicURL).(string)

View File

@ -57,11 +57,6 @@ func NewLoggedBasicAuthenticator(realm string, secrets goauth.SecretProvider) *L
func basicAuth(authenticator *LoggedBasicAuth) func(next http.Handler) http.Handler { func basicAuth(authenticator *LoggedBasicAuth) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// skip auth for unix socket
if IsUnixSocket(r) {
next.ServeHTTP(w, r)
return
}
// skip auth for CORS preflight // skip auth for CORS preflight
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@ -123,11 +118,6 @@ func MiddlewareAuthBasic(user, pass, realm, salt string) Middleware {
func MiddlewareAuthCustom(fn CustomAuthFn, realm string, userFromContext bool) Middleware { func MiddlewareAuthCustom(fn CustomAuthFn, realm string, userFromContext bool) Middleware {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// skip auth for unix socket
if IsUnixSocket(r) {
next.ServeHTTP(w, r)
return
}
// skip auth for CORS preflight // skip auth for CORS preflight
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@ -175,11 +165,6 @@ func MiddlewareCORS(allowOrigin string) Middleware {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// skip cors for unix sockets
if IsUnixSocket(r) {
next.ServeHTTP(w, r)
return
}
if allowOrigin != "" { if allowOrigin != "" {
w.Header().Add("Access-Control-Allow-Origin", allowOrigin) w.Header().Add("Access-Control-Allow-Origin", allowOrigin)

View File

@ -39,8 +39,7 @@ If you set ` + "`--{{ .Prefix }}addr`" + ` to listen on a public or LAN accessib
then using Authentication is advised - see the next section for info. then using Authentication is advised - see the next section for info.
You can use a unix socket by setting the url to ` + "`unix:///path/to/socket`" + ` You can use a unix socket by setting the url to ` + "`unix:///path/to/socket`" + `
or just by using an absolute path name. Note that unix sockets bypass the or just by using an absolute path name.
authentication - this is expected to be done with file system permissions.
` + "`--{{ .Prefix }}addr`" + ` may be repeated to listen on multiple IPs/ports/sockets. ` + "`--{{ .Prefix }}addr`" + ` may be repeated to listen on multiple IPs/ports/sockets.
Socket activation, described further below, can also be used to accomplish the same. Socket activation, described further below, can also be used to accomplish the same.

View File

@ -3,6 +3,7 @@ package http
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt"
"io" "io"
"net" "net"
"net/http" "net/http"
@ -63,46 +64,96 @@ func testReadTestdataFile(t *testing.T, path string) []byte {
} }
func TestNewServerUnix(t *testing.T) { func TestNewServerUnix(t *testing.T) {
ctx := context.Background()
tempDir := t.TempDir() tempDir := t.TempDir()
path := filepath.Join(tempDir, "rclone.sock") path := filepath.Join(tempDir, "rclone.sock")
cfg := DefaultCfg() servers := []struct {
cfg.ListenAddr = []string{path} name string
status int
auth := AuthConfig{ result string
BasicUser: "test", cfg Config
BasicPass: "test", auth AuthConfig
user string
pass string
}{
{
name: "ServerWithoutAuth/NoCreds",
status: http.StatusOK,
result: "hello world",
cfg: Config{
ListenAddr: []string{path},
},
}, {
name: "ServerWithAuth/NoCreds",
status: http.StatusUnauthorized,
cfg: Config{
ListenAddr: []string{path},
},
auth: AuthConfig{
BasicUser: "test",
BasicPass: "test",
},
}, {
name: "ServerWithAuth/GoodCreds",
status: http.StatusOK,
result: "hello world",
cfg: Config{
ListenAddr: []string{path},
},
auth: AuthConfig{
BasicUser: "test",
BasicPass: "test",
},
user: "test",
pass: "test",
}, {
name: "ServerWithAuth/BadCreds",
status: http.StatusUnauthorized,
cfg: Config{
ListenAddr: []string{path},
},
auth: AuthConfig{
BasicUser: "test",
BasicPass: "test",
},
user: "testBAD",
pass: "testBAD",
},
} }
s, err := NewServer(ctx, WithConfig(cfg), WithAuth(auth)) for _, ss := range servers {
require.NoError(t, err) t.Run(ss.name, func(t *testing.T) {
defer func() { s, err := NewServer(context.Background(), WithConfig(ss.cfg), WithAuth(ss.auth))
require.NoError(t, s.Shutdown()) require.NoError(t, err)
_, err := os.Stat(path) defer func() {
require.ErrorIs(t, err, os.ErrNotExist, "shutdown should remove socket") require.NoError(t, s.Shutdown())
}() _, err := os.Stat(path)
require.ErrorIs(t, err, os.ErrNotExist, "shutdown should remove socket")
}()
require.Empty(t, s.URLs(), "unix socket should not appear in URLs") require.Empty(t, s.URLs(), "unix socket should not appear in URLs")
expected := []byte("hello world") s.Router().Mount("/", testEchoHandler([]byte(ss.result)))
s.Router().Mount("/", testEchoHandler(expected)) s.Serve()
s.Serve()
client := testNewHTTPClientUnix(path) client := testNewHTTPClientUnix(path)
req, err := http.NewRequest("GET", "http://unix", nil) req, err := http.NewRequest("GET", "http://unix", nil)
require.NoError(t, err) require.NoError(t, err)
resp, err := client.Do(req) req.SetBasicAuth(ss.user, ss.pass)
require.NoError(t, err)
testExpectRespBody(t, resp, expected) resp, err := client.Do(req)
require.NoError(t, err)
defer func() {
_ = resp.Body.Close()
}()
require.Equal(t, http.StatusOK, resp.StatusCode, "unix sockets should ignore auth") require.Equal(t, ss.status, resp.StatusCode, fmt.Sprintf("should return status %d", ss.status))
for _, key := range _testCORSHeaderKeys { if ss.result != "" {
require.NotContains(t, resp.Header, key, "unix sockets should not be sent CORS headers") testExpectRespBody(t, resp, []byte(ss.result))
}
})
} }
} }