2022-11-08 12:49:19 +01:00
// Package http provides a registration interface for http services
package http
import (
2023-01-11 06:05:44 +01:00
"bytes"
2022-11-08 12:49:19 +01:00
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"html/template"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/go-chi/chi/v5"
"github.com/rclone/rclone/fs/config/flags"
2022-12-22 17:52:43 +01:00
"github.com/rclone/rclone/lib/atexit"
2022-11-08 12:49:19 +01:00
"github.com/spf13/pflag"
)
2023-01-11 06:05:44 +01:00
// Help returns text describing the http server to add to the command
2022-11-08 12:49:19 +01:00
// help.
2023-01-11 06:05:44 +01:00
func Help ( prefix string ) string {
help := `
2022-11-08 12:49:19 +01:00
# # # Server options
2023-01-11 06:05:44 +01:00
Use ` + " ` -- { { . Prefix } } addr ` " + ` to specify which IP address and port the server should
listen on , eg ` + " ` -- { { . Prefix } } addr 1.2 .3 .4 : 8000 ` or ` -- { { . Prefix } } addr : 8080 ` " + ` to listen to all
2022-11-08 12:49:19 +01:00
IPs . By default it only listens on localhost . You can use port
: 0 to let the OS choose an available port .
2023-01-11 06:05:44 +01:00
If you set ` + " ` -- { { . Prefix } } addr ` " + ` to listen on a public or LAN accessible IP address
2022-11-08 12:49:19 +01:00
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`" + `
or just by using an absolute path name . Note that unix sockets bypass the
authentication - this is expected to be done with file system permissions .
2023-01-11 06:05:44 +01:00
` + " ` -- { { . Prefix } } addr ` " + ` may be repeated to listen on multiple IPs / ports / sockets .
2022-11-08 12:49:19 +01:00
2023-01-11 06:05:44 +01:00
` + " ` -- { { . Prefix } } server - read - timeout ` and ` -- { { . Prefix } } server - write - timeout ` " + ` can be used to
2022-11-08 12:49:19 +01:00
control the timeouts on the server . Note that this is the total time
for a transfer .
2023-01-11 06:05:44 +01:00
` + " ` -- { { . Prefix } } max - header - bytes ` " + ` controls the maximum number of bytes the server will
2022-11-08 12:49:19 +01:00
accept in the HTTP header .
2023-01-11 06:05:44 +01:00
` + " ` -- { { . Prefix } } baseurl ` " + ` controls the URL prefix that rclone serves from . By default
rclone will serve from the root . If you used ` + " ` -- { { . Prefix } } baseurl \ "/rclone\"`" + ` then
2022-11-08 12:49:19 +01:00
rclone would serve from a URL starting with "/rclone/" . This is
useful if you wish to proxy rclone serve . Rclone automatically
2023-01-11 06:05:44 +01:00
inserts leading and trailing "/" on ` + " ` -- { { . Prefix } } baseurl ` " + ` , so ` + " ` -- { { . Prefix } } baseurl \ "rclone\"`" + ` ,
` + " ` -- { { . Prefix } } baseurl \ "/rclone\"` and `--{{ .Prefix }}baseurl \"/rclone/\"`" + ` are all treated
2022-11-08 12:49:19 +01:00
identically .
# # # # TLS ( SSL )
By default this will serve over http . If you want you can serve over
2023-01-11 06:05:44 +01:00
https . You will need to supply the ` + " ` -- { { . Prefix } } cert ` and ` -- { { . Prefix } } key ` " + ` flags .
2022-11-08 12:49:19 +01:00
If you wish to do client side certificate validation then you will need to
2023-01-11 06:05:44 +01:00
supply ` + " ` -- { { . Prefix } } client - ca ` " + ` also .
2022-11-08 12:49:19 +01:00
2023-01-11 06:05:44 +01:00
` + " ` -- { { . Prefix } } cert ` " + ` should be a either a PEM encoded certificate or a concatenation
of that with the CA certificate . ` + " ` -- k { { . Prefix } } ey ` " + ` should be the PEM encoded
private key and ` + " ` -- { { . Prefix } } client - ca ` " + ` should be the PEM encoded client
2022-11-08 12:49:19 +01:00
certificate authority certificate .
2023-01-11 06:05:44 +01:00
-- { { . Prefix } } min - tls - version is minimum TLS version that is acceptable . Valid
2022-11-08 12:49:19 +01:00
values are "tls1.0" , "tls1.1" , "tls1.2" and "tls1.3" ( default
"tls1.0" ) .
`
2023-01-11 06:05:44 +01:00
tmpl , err := template . New ( "server help" ) . Parse ( help )
if err != nil {
log . Fatal ( "Fatal error parsing template" , err )
}
data := struct {
Prefix string
} {
Prefix : prefix ,
}
buf := & bytes . Buffer { }
err = tmpl . Execute ( buf , data )
if err != nil {
log . Fatal ( "Fatal error executing template" , err )
}
return buf . String ( )
}
2022-11-08 12:49:19 +01:00
// Middleware function signature required by chi.Router.Use()
type Middleware func ( http . Handler ) http . Handler
2022-11-08 13:03:26 +01:00
// Config contains options for the http Server
type Config struct {
2022-11-08 12:49:19 +01:00
ListenAddr [ ] string // Port to listen on
BaseURL string // prefix to strip from URLs
ServerReadTimeout time . Duration // Timeout for server reading data
ServerWriteTimeout time . Duration // Timeout for server writing data
MaxHeaderBytes int // Maximum size of request header
TLSCert string // Path to TLS PEM key (concatenation of certificate and CA certificate)
TLSKey string // Path to TLS PEM Private key
TLSCertBody [ ] byte // TLS PEM key (concatenation of certificate and CA certificate) body, ignores TLSCert
TLSKeyBody [ ] byte // TLS PEM Private key body, ignores TLSKey
ClientCA string // Client certificate authority to verify clients with
MinTLSVersion string // MinTLSVersion contains the minimum TLS version that is acceptable.
2023-07-26 11:15:54 +02:00
AllowOrigin string // AllowOrigin sets the Access-Control-Allow-Origin header
2022-11-08 12:49:19 +01:00
}
// AddFlagsPrefix adds flags for the httplib
2022-11-08 13:03:26 +01:00
func ( cfg * Config ) AddFlagsPrefix ( flagSet * pflag . FlagSet , prefix string ) {
2023-07-10 19:34:10 +02:00
flags . StringArrayVarP ( flagSet , & cfg . ListenAddr , prefix + "addr" , "" , cfg . ListenAddr , "IPaddress:Port or :Port to bind server to" , prefix )
flags . DurationVarP ( flagSet , & cfg . ServerReadTimeout , prefix + "server-read-timeout" , "" , cfg . ServerReadTimeout , "Timeout for server reading data" , prefix )
flags . DurationVarP ( flagSet , & cfg . ServerWriteTimeout , prefix + "server-write-timeout" , "" , cfg . ServerWriteTimeout , "Timeout for server writing data" , prefix )
flags . IntVarP ( flagSet , & cfg . MaxHeaderBytes , prefix + "max-header-bytes" , "" , cfg . MaxHeaderBytes , "Maximum size of request header" , prefix )
flags . StringVarP ( flagSet , & cfg . TLSCert , prefix + "cert" , "" , cfg . TLSCert , "TLS PEM key (concatenation of certificate and CA certificate)" , prefix )
flags . StringVarP ( flagSet , & cfg . TLSKey , prefix + "key" , "" , cfg . TLSKey , "TLS PEM Private key" , prefix )
flags . StringVarP ( flagSet , & cfg . ClientCA , prefix + "client-ca" , "" , cfg . ClientCA , "Client certificate authority to verify clients with" , prefix )
flags . StringVarP ( flagSet , & cfg . BaseURL , prefix + "baseurl" , "" , cfg . BaseURL , "Prefix for URLs - leave blank for root" , prefix )
flags . StringVarP ( flagSet , & cfg . MinTLSVersion , prefix + "min-tls-version" , "" , cfg . MinTLSVersion , "Minimum TLS version that is acceptable" , prefix )
flags . StringVarP ( flagSet , & cfg . AllowOrigin , prefix + "allow-origin" , "" , cfg . AllowOrigin , "Origin which cross-domain request (CORS) can be executed from" , prefix )
2022-11-08 12:49:19 +01:00
}
// AddHTTPFlagsPrefix adds flags for the httplib
2022-11-08 13:03:26 +01:00
func AddHTTPFlagsPrefix ( flagSet * pflag . FlagSet , prefix string , cfg * Config ) {
2022-11-08 12:49:19 +01:00
cfg . AddFlagsPrefix ( flagSet , prefix )
}
2022-11-08 13:03:26 +01:00
// DefaultCfg is the default values used for Config
func DefaultCfg ( ) Config {
return Config {
2022-11-08 12:49:19 +01:00
ListenAddr : [ ] string { "127.0.0.1:8080" } ,
ServerReadTimeout : 1 * time . Hour ,
ServerWriteTimeout : 1 * time . Hour ,
MaxHeaderBytes : 4096 ,
MinTLSVersion : "tls1.0" ,
}
}
type instance struct {
url string
listener net . Listener
httpServer * http . Server
}
func ( s instance ) serve ( wg * sync . WaitGroup ) {
defer wg . Done ( )
err := s . httpServer . Serve ( s . listener )
if err != http . ErrServerClosed && err != nil {
log . Printf ( "%s: unexpected error: %s" , s . listener . Addr ( ) , err . Error ( ) )
}
}
2022-11-23 10:44:53 +01:00
// Server contains info about the running http server
type Server struct {
2022-11-08 12:49:19 +01:00
wg sync . WaitGroup
mux chi . Router
tlsConfig * tls . Config
instances [ ] instance
auth AuthConfig
2022-11-08 13:03:26 +01:00
cfg Config
2022-11-08 12:49:19 +01:00
template * TemplateConfig
htmlTemplate * template . Template
2022-12-10 11:51:57 +01:00
usingAuth bool // set if we are using auth middleware
2022-12-22 17:52:43 +01:00
atexitHandle atexit . FnHandle
2022-11-08 12:49:19 +01:00
}
// Option allows customizing the server
2022-11-23 10:44:53 +01:00
type Option func ( * Server )
2022-11-08 12:49:19 +01:00
// WithAuth option initializes the appropriate auth middleware
func WithAuth ( cfg AuthConfig ) Option {
2022-11-23 10:44:53 +01:00
return func ( s * Server ) {
2022-11-08 12:49:19 +01:00
s . auth = cfg
}
}
2022-11-08 13:03:26 +01:00
// WithConfig option applies the Config to the server, overriding defaults
func WithConfig ( cfg Config ) Option {
2022-11-23 10:44:53 +01:00
return func ( s * Server ) {
2022-11-08 12:49:19 +01:00
s . cfg = cfg
}
}
// WithTemplate option allows the parsing of a template
func WithTemplate ( cfg TemplateConfig ) Option {
2022-11-23 10:44:53 +01:00
return func ( s * Server ) {
2022-11-08 12:49:19 +01:00
s . template = & cfg
}
}
// NewServer instantiates a new http server using provided listeners and options
// This function is provided if the default http server does not meet a services requirements and should not generally be used
// A http server can listen using multiple listeners. For example, a listener for port 80, and a listener for port 443.
// tlsListeners are ignored if opt.TLSKey is not provided
2022-11-23 10:44:53 +01:00
func NewServer ( ctx context . Context , options ... Option ) ( * Server , error ) {
s := & Server {
2022-11-08 12:49:19 +01:00
mux : chi . NewRouter ( ) ,
2022-11-08 13:03:26 +01:00
cfg : DefaultCfg ( ) ,
2022-11-08 12:49:19 +01:00
}
2022-11-23 10:44:53 +01:00
// Make sure default logger is logging where everything else is
// middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{Logger: log.Default(), NoColor: true})
// Log requests
// s.mux.Use(middleware.Logger)
2022-11-08 12:49:19 +01:00
for _ , opt := range options {
opt ( s )
}
// Build base router
s . mux . MethodNotAllowed ( func ( w http . ResponseWriter , _ * http . Request ) {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
} )
s . mux . NotFound ( func ( w http . ResponseWriter , _ * http . Request ) {
http . Error ( w , http . StatusText ( http . StatusNotFound ) , http . StatusNotFound )
} )
// Ignore passing "/" for BaseURL
s . cfg . BaseURL = strings . Trim ( s . cfg . BaseURL , "/" )
if s . cfg . BaseURL != "" {
s . cfg . BaseURL = "/" + s . cfg . BaseURL
s . mux . Use ( MiddlewareStripPrefix ( s . cfg . BaseURL ) )
}
err := s . initTemplate ( )
if err != nil {
return nil , err
}
err = s . initTLS ( )
if err != nil {
return nil , err
}
2023-07-26 11:15:54 +02:00
s . mux . Use ( MiddlewareCORS ( s . cfg . AllowOrigin ) )
2023-05-26 07:26:13 +02:00
s . initAuth ( )
2022-11-08 12:49:19 +01:00
for _ , addr := range s . cfg . ListenAddr {
var url string
var network = "tcp"
var tlsCfg * tls . Config
if strings . HasPrefix ( addr , "unix://" ) || filepath . IsAbs ( addr ) {
network = "unix"
addr = strings . TrimPrefix ( addr , "unix://" )
url = addr
} else if strings . HasPrefix ( addr , "tls://" ) || ( len ( s . cfg . ListenAddr ) == 1 && s . tlsConfig != nil ) {
tlsCfg = s . tlsConfig
addr = strings . TrimPrefix ( addr , "tls://" )
}
var listener net . Listener
if tlsCfg == nil {
listener , err = net . Listen ( network , addr )
} else {
listener , err = tls . Listen ( network , addr , tlsCfg )
}
if err != nil {
return nil , err
}
if network == "tcp" {
var secure string
if tlsCfg != nil {
secure = "s"
}
url = fmt . Sprintf ( "http%s://%s%s/" , secure , listener . Addr ( ) . String ( ) , s . cfg . BaseURL )
}
ii := instance {
url : url ,
listener : listener ,
httpServer : & http . Server {
Handler : s . mux ,
ReadTimeout : s . cfg . ServerReadTimeout ,
WriteTimeout : s . cfg . ServerWriteTimeout ,
MaxHeaderBytes : s . cfg . MaxHeaderBytes ,
ReadHeaderTimeout : 10 * time . Second , // time to send the headers
IdleTimeout : 60 * time . Second , // time to keep idle connections open
TLSConfig : tlsCfg ,
BaseContext : NewBaseContext ( ctx , url ) ,
} ,
}
s . instances = append ( s . instances , ii )
}
return s , nil
}
2022-11-23 10:44:53 +01:00
func ( s * Server ) initAuth ( ) {
2023-05-26 07:26:13 +02:00
s . usingAuth = false
authCertificateUserEnabled := s . tlsConfig != nil && s . tlsConfig . ClientAuth != tls . NoClientCert && s . auth . HtPasswd == "" && s . auth . BasicUser == ""
if authCertificateUserEnabled {
s . usingAuth = true
s . mux . Use ( MiddlewareAuthCertificateUser ( ) )
}
2022-11-08 12:49:19 +01:00
if s . auth . CustomAuthFn != nil {
2022-12-10 11:51:57 +01:00
s . usingAuth = true
2023-05-26 07:26:13 +02:00
s . mux . Use ( MiddlewareAuthCustom ( s . auth . CustomAuthFn , s . auth . Realm , authCertificateUserEnabled ) )
2022-11-08 12:49:19 +01:00
return
}
if s . auth . HtPasswd != "" {
2022-12-10 11:51:57 +01:00
s . usingAuth = true
2022-11-08 12:49:19 +01:00
s . mux . Use ( MiddlewareAuthHtpasswd ( s . auth . HtPasswd , s . auth . Realm ) )
return
}
if s . auth . BasicUser != "" {
2022-12-10 11:51:57 +01:00
s . usingAuth = true
2022-11-08 12:49:19 +01:00
s . mux . Use ( MiddlewareAuthBasic ( s . auth . BasicUser , s . auth . BasicPass , s . auth . Realm , s . auth . Salt ) )
return
}
}
2022-11-23 10:44:53 +01:00
func ( s * Server ) initTemplate ( ) error {
2022-11-08 12:49:19 +01:00
if s . template == nil {
return nil
}
var err error
s . htmlTemplate , err = GetTemplate ( s . template . Path )
if err != nil {
err = fmt . Errorf ( "failed to get template: %w" , err )
}
return err
}
var (
2022-11-08 13:03:26 +01:00
// ErrInvalidMinTLSVersion - hard coded errors, allowing for easier testing
2022-11-08 12:49:19 +01:00
ErrInvalidMinTLSVersion = errors . New ( "invalid value for --min-tls-version" )
2022-11-08 13:03:26 +01:00
// ErrTLSBodyMismatch - hard coded errors, allowing for easier testing
ErrTLSBodyMismatch = errors . New ( "need both TLSCertBody and TLSKeyBody to use TLS" )
// ErrTLSFileMismatch - hard coded errors, allowing for easier testing
ErrTLSFileMismatch = errors . New ( "need both --cert and --key to use TLS" )
// ErrTLSParseCA - hard coded errors, allowing for easier testing
ErrTLSParseCA = errors . New ( "unable to parse client certificate authority" )
2022-11-08 12:49:19 +01:00
)
2022-11-23 10:44:53 +01:00
func ( s * Server ) initTLS ( ) error {
2022-11-08 12:49:19 +01:00
if s . cfg . TLSCert == "" && s . cfg . TLSKey == "" && len ( s . cfg . TLSCertBody ) == 0 && len ( s . cfg . TLSKeyBody ) == 0 {
return nil
}
if ( len ( s . cfg . TLSCertBody ) > 0 ) != ( len ( s . cfg . TLSKeyBody ) > 0 ) {
return ErrTLSBodyMismatch
}
if ( s . cfg . TLSCert != "" ) != ( s . cfg . TLSKey != "" ) {
return ErrTLSFileMismatch
}
var cert tls . Certificate
var err error
if len ( s . cfg . TLSCertBody ) > 0 {
cert , err = tls . X509KeyPair ( s . cfg . TLSCertBody , s . cfg . TLSKeyBody )
} else {
cert , err = tls . LoadX509KeyPair ( s . cfg . TLSCert , s . cfg . TLSKey )
}
if err != nil {
return err
}
var minTLSVersion uint16
switch s . cfg . MinTLSVersion {
case "tls1.0" :
minTLSVersion = tls . VersionTLS10
case "tls1.1" :
minTLSVersion = tls . VersionTLS11
case "tls1.2" :
minTLSVersion = tls . VersionTLS12
case "tls1.3" :
minTLSVersion = tls . VersionTLS13
default :
return fmt . Errorf ( "%w: %s" , ErrInvalidMinTLSVersion , s . cfg . MinTLSVersion )
}
s . tlsConfig = & tls . Config {
MinVersion : minTLSVersion ,
Certificates : [ ] tls . Certificate { cert } ,
}
if s . cfg . ClientCA != "" {
// if !useTLS {
// err := errors.New("can't use --client-ca without --cert and --key")
// log.Fatalf(err.Error())
// }
certpool := x509 . NewCertPool ( )
pem , err := os . ReadFile ( s . cfg . ClientCA )
if err != nil {
return err
}
if ! certpool . AppendCertsFromPEM ( pem ) {
return ErrTLSParseCA
}
s . tlsConfig . ClientCAs = certpool
s . tlsConfig . ClientAuth = tls . RequireAndVerifyClientCert
}
return nil
}
// Serve starts the HTTP server on each listener
2022-11-23 10:44:53 +01:00
func ( s * Server ) Serve ( ) {
2022-11-08 12:49:19 +01:00
s . wg . Add ( len ( s . instances ) )
for _ , ii := range s . instances {
// TODO: decide how/when to log listening url
// log.Printf("listening on %s", ii.url)
go ii . serve ( & s . wg )
}
2022-12-22 17:52:43 +01:00
// Install an atexit handler to shutdown gracefully
s . atexitHandle = atexit . Register ( func ( ) { _ = s . Shutdown ( ) } )
2022-11-08 12:49:19 +01:00
}
// Wait blocks while the server is serving requests
2022-11-23 10:44:53 +01:00
func ( s * Server ) Wait ( ) {
2022-11-08 12:49:19 +01:00
s . wg . Wait ( )
}
// Router returns the server base router
2022-11-23 10:44:53 +01:00
func ( s * Server ) Router ( ) chi . Router {
2022-11-08 12:49:19 +01:00
return s . mux
}
2022-12-22 17:52:43 +01:00
// Time to wait to Shutdown an HTTP server
const gracefulShutdownTime = 10 * time . Second
2022-11-08 12:49:19 +01:00
// Shutdown gracefully shuts down the server
2022-11-23 10:44:53 +01:00
func ( s * Server ) Shutdown ( ) error {
2022-12-22 17:52:43 +01:00
// Stop the atexit handler
if s . atexitHandle != nil {
atexit . Unregister ( s . atexitHandle )
s . atexitHandle = nil
}
2022-11-08 12:49:19 +01:00
for _ , ii := range s . instances {
2022-12-22 17:52:43 +01:00
expiry := time . Now ( ) . Add ( gracefulShutdownTime )
ctx , cancel := context . WithDeadline ( context . Background ( ) , expiry )
2022-11-08 12:49:19 +01:00
if err := ii . httpServer . Shutdown ( ctx ) ; err != nil {
log . Printf ( "error shutting down server: %s" , err )
}
2022-12-22 17:52:43 +01:00
cancel ( )
2022-11-08 12:49:19 +01:00
}
s . wg . Wait ( )
return nil
}
// HTMLTemplate returns the parsed template, if WithTemplate option was passed.
2022-11-23 10:44:53 +01:00
func ( s * Server ) HTMLTemplate ( ) * template . Template {
2022-11-08 12:49:19 +01:00
return s . htmlTemplate
}
// URLs returns all configured URLS
2022-11-23 10:44:53 +01:00
func ( s * Server ) URLs ( ) [ ] string {
2022-11-08 12:49:19 +01:00
var out [ ] string
for _ , ii := range s . instances {
if ii . listener . Addr ( ) . Network ( ) == "unix" {
continue
}
out = append ( out , ii . url )
}
return out
}
2022-12-10 11:51:57 +01:00
// UsingAuth returns true if authentication is required
func ( s * Server ) UsingAuth ( ) bool {
return s . usingAuth
}