mirror of
https://github.com/restic/rest-server.git
synced 2025-10-18 23:23:19 +00:00
Add support for proxy-based authentication
This commit is contained in:
parent
f053e33486
commit
b0a9a0452e
6 changed files with 106 additions and 7 deletions
8
changelog/unreleased/pull-307
Normal file
8
changelog/unreleased/pull-307
Normal file
|
@ -0,0 +1,8 @@
|
|||
Enhancement: Add support for proxy-based authentication
|
||||
|
||||
The server now supports authentication via a proxy header specified with the --proxy-auth flag (e.g., --proxy-auth=X-Forwarded-User).
|
||||
When this flag is set, the server will authenticate users based on the given header and disable BasicAuth.
|
||||
Note that --proxy-auth is ignored if --no-auth is set, as --no-auth disables all authentication.
|
||||
|
||||
https://github.com/restic/rest-server/issues/174
|
||||
https://github.com/restic/rest-server/pull/307
|
|
@ -61,8 +61,9 @@ func newRestServerApp() *restServerApp {
|
|||
flags.BoolVar(&rv.Server.TLS, "tls", rv.Server.TLS, "turn on TLS support")
|
||||
flags.StringVar(&rv.Server.TLSCert, "tls-cert", rv.Server.TLSCert, "TLS certificate path")
|
||||
flags.StringVar(&rv.Server.TLSKey, "tls-key", rv.Server.TLSKey, "TLS key path")
|
||||
flags.BoolVar(&rv.Server.NoAuth, "no-auth", rv.Server.NoAuth, "disable .htpasswd authentication")
|
||||
flags.BoolVar(&rv.Server.NoAuth, "no-auth", rv.Server.NoAuth, "disable authentication")
|
||||
flags.StringVar(&rv.Server.HtpasswdPath, "htpasswd-file", rv.Server.HtpasswdPath, "location of .htpasswd file (default: \"<data directory>/.htpasswd)\"")
|
||||
flags.StringVar(&rv.Server.ProxyAuthUsername, "proxy-auth-username", rv.Server.ProxyAuthUsername, "specifies the HTTP header containing the username for proxy-based authentication")
|
||||
flags.BoolVar(&rv.Server.NoVerifyUpload, "no-verify-upload", rv.Server.NoVerifyUpload,
|
||||
"do not verify the integrity of uploaded data. DO NOT enable unless the rest-server runs on a very low-power device")
|
||||
flags.BoolVar(&rv.Server.AppendOnly, "append-only", rv.Server.AppendOnly, "enable append only mode")
|
||||
|
@ -130,7 +131,11 @@ func (app *restServerApp) runRoot(_ *cobra.Command, _ []string) error {
|
|||
if app.Server.NoAuth {
|
||||
log.Println("Authentication disabled")
|
||||
} else {
|
||||
log.Println("Authentication enabled")
|
||||
if app.Server.ProxyAuthUsername == "" {
|
||||
log.Println("Authentication enabled")
|
||||
} else {
|
||||
log.Println("Proxy Authentication enabled.")
|
||||
}
|
||||
}
|
||||
|
||||
handler, err := restserver.NewHandler(&app.Server)
|
||||
|
|
|
@ -118,6 +118,12 @@ func TestGetHandler(t *testing.T) {
|
|||
t.Errorf("NoAuth=true: expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// With NoAuth = false, no .htpasswd and ProxyAuth = X-Remote-User
|
||||
_, err = getHandler(&restserver.Server{Path: dir, ProxyAuthUsername: "X-Remote-User"})
|
||||
if err != nil {
|
||||
t.Errorf("NoAuth=false, ProxyAuthUsername = X-Remote-User: expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// With NoAuth = false and custom .htpasswd
|
||||
htpFile, err := os.CreateTemp(dir, "custom")
|
||||
if err != nil {
|
||||
|
|
|
@ -24,6 +24,7 @@ type Server struct {
|
|||
TLSCert string
|
||||
TLS bool
|
||||
NoAuth bool
|
||||
ProxyAuthUsername string
|
||||
AppendOnly bool
|
||||
PrivateRepos bool
|
||||
Prometheus bool
|
||||
|
|
17
mux.go
17
mux.go
|
@ -41,10 +41,17 @@ func (s *Server) checkAuth(r *http.Request) (username string, ok bool) {
|
|||
if s.NoAuth {
|
||||
return username, true
|
||||
}
|
||||
var password string
|
||||
username, password, ok = r.BasicAuth()
|
||||
if !ok || !s.htpasswdFile.Validate(username, password) {
|
||||
return "", false
|
||||
if s.ProxyAuthUsername != "" {
|
||||
username = r.Header.Get(s.ProxyAuthUsername)
|
||||
if username == "" {
|
||||
return "", false
|
||||
}
|
||||
} else {
|
||||
var password string
|
||||
username, password, ok = r.BasicAuth()
|
||||
if !ok || !s.htpasswdFile.Validate(username, password) {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return username, true
|
||||
}
|
||||
|
@ -66,7 +73,7 @@ func (s *Server) wrapMetricsAuth(f http.HandlerFunc) http.HandlerFunc {
|
|||
|
||||
// NewHandler returns the master HTTP multiplexer/router.
|
||||
func NewHandler(server *Server) (http.Handler, error) {
|
||||
if !server.NoAuth {
|
||||
if !server.NoAuth && server.ProxyAuthUsername == "" {
|
||||
var err error
|
||||
if server.HtpasswdPath == "" {
|
||||
server.HtpasswdPath = filepath.Join(server.Path, ".htpasswd")
|
||||
|
|
72
mux_test.go
Normal file
72
mux_test.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package restserver
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckAuth(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
server *Server
|
||||
requestHeaders map[string]string
|
||||
basicAuth bool
|
||||
basicUser string
|
||||
basicPassword string
|
||||
expectedUser string
|
||||
expectedOk bool
|
||||
}{
|
||||
{
|
||||
name: "NoAuth enabled",
|
||||
server: &Server{
|
||||
NoAuth: true,
|
||||
},
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "Proxy Auth successful",
|
||||
server: &Server{
|
||||
ProxyAuthUsername: "X-Remote-User",
|
||||
},
|
||||
requestHeaders: map[string]string{
|
||||
"X-Remote-User": "restic",
|
||||
},
|
||||
expectedUser: "restic",
|
||||
expectedOk: true,
|
||||
},
|
||||
{
|
||||
name: "Proxy Auth empty header",
|
||||
server: &Server{
|
||||
ProxyAuthUsername: "X-Remote-User",
|
||||
},
|
||||
requestHeaders: map[string]string{
|
||||
"X-Remote-User": "",
|
||||
},
|
||||
expectedOk: false,
|
||||
},
|
||||
{
|
||||
name: "Proxy Auth missing header",
|
||||
server: &Server{
|
||||
ProxyAuthUsername: "X-Remote-User",
|
||||
},
|
||||
expectedOk: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
for header, value := range tt.requestHeaders {
|
||||
req.Header.Set(header, value)
|
||||
}
|
||||
if tt.basicAuth {
|
||||
req.SetBasicAuth(tt.basicUser, tt.basicPassword)
|
||||
}
|
||||
|
||||
username, ok := tt.server.checkAuth(req)
|
||||
if username != tt.expectedUser || ok != tt.expectedOk {
|
||||
t.Errorf("expected (%v, %v), got (%v, %v)", tt.expectedUser, tt.expectedOk, username, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue