httpd service: add CORS support
This commit is contained in:
parent
ee5c5e033d
commit
0833b4698e
10 changed files with 78 additions and 18 deletions
|
@ -195,7 +195,7 @@ func Init() {
|
|||
CertificateKeyFile: "",
|
||||
CACertificates: []string{},
|
||||
CARevocationLists: []string{},
|
||||
Cors: webdavd.Cors{
|
||||
Cors: webdavd.CorsConfig{
|
||||
Enabled: false,
|
||||
AllowedOrigins: []string{},
|
||||
AllowedMethods: []string{},
|
||||
|
@ -280,6 +280,15 @@ func Init() {
|
|||
CARevocationLists: nil,
|
||||
SigningPassphrase: "",
|
||||
MaxUploadFileSize: 1048576000,
|
||||
Cors: httpd.CorsConfig{
|
||||
Enabled: false,
|
||||
AllowedOrigins: []string{},
|
||||
AllowedMethods: []string{},
|
||||
AllowedHeaders: []string{},
|
||||
ExposedHeaders: []string{},
|
||||
AllowCredentials: false,
|
||||
MaxAge: 0,
|
||||
},
|
||||
},
|
||||
HTTPConfig: httpclient.Config{
|
||||
Timeout: 20,
|
||||
|
@ -1217,6 +1226,13 @@ func setViperDefaults() {
|
|||
viper.SetDefault("httpd.ca_revocation_lists", globalConf.HTTPDConfig.CARevocationLists)
|
||||
viper.SetDefault("httpd.signing_passphrase", globalConf.HTTPDConfig.SigningPassphrase)
|
||||
viper.SetDefault("httpd.max_upload_file_size", globalConf.HTTPDConfig.MaxUploadFileSize)
|
||||
viper.SetDefault("httpd.cors.enabled", globalConf.HTTPDConfig.Cors.Enabled)
|
||||
viper.SetDefault("httpd.cors.allowed_origins", globalConf.HTTPDConfig.Cors.AllowedOrigins)
|
||||
viper.SetDefault("httpd.cors.allowed_methods", globalConf.HTTPDConfig.Cors.AllowedMethods)
|
||||
viper.SetDefault("httpd.cors.allowed_headers", globalConf.HTTPDConfig.Cors.AllowedHeaders)
|
||||
viper.SetDefault("httpd.cors.exposed_headers", globalConf.HTTPDConfig.Cors.ExposedHeaders)
|
||||
viper.SetDefault("httpd.cors.allow_credentials", globalConf.HTTPDConfig.Cors.AllowCredentials)
|
||||
viper.SetDefault("httpd.cors.max_age", globalConf.HTTPDConfig.Cors.MaxAge)
|
||||
viper.SetDefault("http.timeout", globalConf.HTTPConfig.Timeout)
|
||||
viper.SetDefault("http.retry_wait_min", globalConf.HTTPConfig.RetryWaitMin)
|
||||
viper.SetDefault("http.retry_wait_max", globalConf.HTTPConfig.RetryWaitMax)
|
||||
|
|
|
@ -232,6 +232,14 @@ The configuration file contains the following sections:
|
|||
- `ca_revocation_lists`, list of strings. Set a revocation lists, one for each root CA, to be used to check if a client certificate has been revoked. The revocation lists can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
|
||||
- `signing_passphrase`, string. Passphrase to use to derive the signing key for JWT and CSRF tokens. If empty a random signing key will be generated each time SFTPGo starts. If you set a signing passphrase you should consider rotating it periodically for added security.
|
||||
- `max_upload_file_size`, integer. Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests. 0 means no limit. Default: 1048576000.
|
||||
- `cors` struct containing CORS configuration. SFTPGo uses [Go CORS handler](https://github.com/rs/cors), please refer to upstream documentation for fields meaning and their default values.
|
||||
- `enabled`, boolean, set to true to enable CORS.
|
||||
- `allowed_origins`, list of strings.
|
||||
- `allowed_methods`, list of strings.
|
||||
- `allowed_headers`, list of strings.
|
||||
- `exposed_headers`, list of strings.
|
||||
- `allow_credentials` boolean.
|
||||
- `max_age`, integer.
|
||||
- **"telemetry"**, the configuration for the telemetry server, more details [below](#telemetry-server)
|
||||
- `bind_port`, integer. The port used for serving HTTP requests. Set to 0 to disable HTTP server. Default: 10000
|
||||
- `bind_address`, string. Leave blank to listen on all available network interfaces. On \*NIX you can specify an absolute path to listen on a Unix-domain socket. Default: "127.0.0.1"
|
||||
|
|
4
go.mod
4
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
|
||||
github.com/aws/aws-sdk-go v1.42.4
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.1
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.3
|
||||
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
|
||||
github.com/fclairamb/ftpserverlib v0.16.0
|
||||
github.com/fclairamb/go-log v0.1.0
|
||||
|
@ -25,7 +25,7 @@ require (
|
|||
github.com/hashicorp/go-retryablehttp v0.7.0
|
||||
github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126
|
||||
github.com/klauspost/compress v1.13.6
|
||||
github.com/lestrrat-go/jwx v1.2.10
|
||||
github.com/lestrrat-go/jwx v1.2.11
|
||||
github.com/lib/pq v1.10.4
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
|
|
8
go.sum
8
go.sum
|
@ -192,8 +192,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
|||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.1 h1:nZte1DDdL9iu8IV0YPmX8l9Lg2+HRJ3CMvkT3iG52rc=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.1/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.3 h1:2881elKwTMrAWuSP2N/4PtU6XyqoyI55Fv3TSTD+Efo=
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.2.3/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
|
@ -566,8 +566,8 @@ github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++
|
|||
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.2.6/go.mod h1:tJuGuAI3LC71IicTx82Mz1n3w9woAs2bYJZpkjJQ5aU=
|
||||
github.com/lestrrat-go/jwx v1.2.10 h1:rz6Ywm3wCRWsy2lyRZ7uHzE4E09m7X9eINaoAEVXCKw=
|
||||
github.com/lestrrat-go/jwx v1.2.10/go.mod h1:25DcLbNWArPA/Ew5CcBmewl32cJKxOk5cbepBsIJFzw=
|
||||
github.com/lestrrat-go/jwx v1.2.11 h1:e9BS5NQ003hxXogNsgf5fEWf01ZJvj4Aj1qy7Dykqm8=
|
||||
github.com/lestrrat-go/jwx v1.2.11/go.mod h1:25DcLbNWArPA/Ew5CcBmewl32cJKxOk5cbepBsIJFzw=
|
||||
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
|
|
|
@ -319,6 +319,17 @@ type ServicesStatus struct {
|
|||
MFA mfa.ServiceStatus `json:"mfa"`
|
||||
}
|
||||
|
||||
// CorsConfig defines the CORS configuration
|
||||
type CorsConfig struct {
|
||||
AllowedOrigins []string `json:"allowed_origins" mapstructure:"allowed_origins"`
|
||||
AllowedMethods []string `json:"allowed_methods" mapstructure:"allowed_methods"`
|
||||
AllowedHeaders []string `json:"allowed_headers" mapstructure:"allowed_headers"`
|
||||
ExposedHeaders []string `json:"exposed_headers" mapstructure:"exposed_headers"`
|
||||
AllowCredentials bool `json:"allow_credentials" mapstructure:"allow_credentials"`
|
||||
Enabled bool `json:"enabled" mapstructure:"enabled"`
|
||||
MaxAge int `json:"max_age" mapstructure:"max_age"`
|
||||
}
|
||||
|
||||
// Conf httpd daemon configuration
|
||||
type Conf struct {
|
||||
// Addresses and ports to bind to
|
||||
|
@ -351,6 +362,8 @@ type Conf struct {
|
|||
// MaxUploadFileSize Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests.
|
||||
// 0 means no limit
|
||||
MaxUploadFileSize int64 `json:"max_upload_file_size" mapstructure:"max_upload_file_size"`
|
||||
// CORS configuration
|
||||
Cors CorsConfig `json:"cors" mapstructure:"cors"`
|
||||
}
|
||||
|
||||
type apiResponse struct {
|
||||
|
@ -456,7 +469,7 @@ func (c *Conf) Initialize(configDir string) error {
|
|||
}
|
||||
|
||||
go func(b Binding) {
|
||||
server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase)
|
||||
server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase, c.Cors)
|
||||
|
||||
exitChannel <- server.listenAndServe()
|
||||
}(binding)
|
||||
|
@ -600,7 +613,7 @@ func GetHTTPRouter() http.Handler {
|
|||
EnableWebAdmin: true,
|
||||
EnableWebClient: true,
|
||||
}
|
||||
server := newHttpdServer(b, "../static", "")
|
||||
server := newHttpdServer(b, "../static", "", CorsConfig{})
|
||||
server.initializeRouter()
|
||||
return server.router
|
||||
}
|
||||
|
|
|
@ -1406,7 +1406,7 @@ func TestProxyHeaders(t *testing.T) {
|
|||
}
|
||||
err = b.parseAllowedProxy()
|
||||
assert.NoError(t, err)
|
||||
server := newHttpdServer(b, "", "")
|
||||
server := newHttpdServer(b, "", "", CorsConfig{Enabled: true})
|
||||
server.initializeRouter()
|
||||
testServer := httptest.NewServer(server.router)
|
||||
defer testServer.Close()
|
||||
|
@ -1492,7 +1492,7 @@ func TestRecoverer(t *testing.T) {
|
|||
EnableWebAdmin: true,
|
||||
EnableWebClient: false,
|
||||
}
|
||||
server := newHttpdServer(b, "../static", "")
|
||||
server := newHttpdServer(b, "../static", "", CorsConfig{})
|
||||
server.initializeRouter()
|
||||
server.router.Get(recoveryPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
panic("panic")
|
||||
|
@ -1607,7 +1607,7 @@ func TestWebAdminRedirect(t *testing.T) {
|
|||
EnableWebAdmin: true,
|
||||
EnableWebClient: false,
|
||||
}
|
||||
server := newHttpdServer(b, "../static", "")
|
||||
server := newHttpdServer(b, "../static", "", CorsConfig{})
|
||||
server.initializeRouter()
|
||||
testServer := httptest.NewServer(server.router)
|
||||
defer testServer.Close()
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/go-chi/jwtauth/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/rs/cors"
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/common"
|
||||
|
@ -41,15 +42,17 @@ type httpdServer struct {
|
|||
router *chi.Mux
|
||||
tokenAuth *jwtauth.JWTAuth
|
||||
signingPassphrase string
|
||||
cors CorsConfig
|
||||
}
|
||||
|
||||
func newHttpdServer(b Binding, staticFilesPath, signingPassphrase string) *httpdServer {
|
||||
func newHttpdServer(b Binding, staticFilesPath, signingPassphrase string, cors CorsConfig) *httpdServer {
|
||||
return &httpdServer{
|
||||
binding: b,
|
||||
staticFilesPath: staticFilesPath,
|
||||
enableWebAdmin: b.EnableWebAdmin,
|
||||
enableWebClient: b.EnableWebClient,
|
||||
signingPassphrase: signingPassphrase,
|
||||
cors: cors,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -943,6 +946,17 @@ func (s *httpdServer) initializeRouter() {
|
|||
s.router.Use(s.checkConnection)
|
||||
s.router.Use(logger.NewStructuredLogger(logger.GetLogger()))
|
||||
s.router.Use(recoverer)
|
||||
if s.cors.Enabled {
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: s.cors.AllowedOrigins,
|
||||
AllowedMethods: s.cors.AllowedMethods,
|
||||
AllowedHeaders: s.cors.AllowedHeaders,
|
||||
ExposedHeaders: s.cors.ExposedHeaders,
|
||||
MaxAge: s.cors.MaxAge,
|
||||
AllowCredentials: s.cors.AllowCredentials,
|
||||
})
|
||||
s.router.Use(c.Handler)
|
||||
}
|
||||
s.router.Use(middleware.GetHead)
|
||||
s.router.Use(middleware.StripSlashes)
|
||||
|
||||
|
|
11
sftpgo.json
11
sftpgo.json
|
@ -221,7 +221,16 @@
|
|||
"ca_certificates": [],
|
||||
"ca_revocation_lists": [],
|
||||
"signing_passphrase": "",
|
||||
"max_upload_file_size": 1048576000
|
||||
"max_upload_file_size": 1048576000,
|
||||
"cors": {
|
||||
"enabled": false,
|
||||
"allowed_origins": [],
|
||||
"allowed_methods": [],
|
||||
"allowed_headers": [],
|
||||
"exposed_headers": [],
|
||||
"allow_credentials": false,
|
||||
"max_age": 0
|
||||
}
|
||||
},
|
||||
"telemetry": {
|
||||
"bind_port": 10000,
|
||||
|
|
|
@ -37,8 +37,8 @@ type ServiceStatus struct {
|
|||
Bindings []Binding `json:"bindings"`
|
||||
}
|
||||
|
||||
// Cors configuration
|
||||
type Cors struct {
|
||||
// CorsConfig defines the CORS configuration
|
||||
type CorsConfig struct {
|
||||
AllowedOrigins []string `json:"allowed_origins" mapstructure:"allowed_origins"`
|
||||
AllowedMethods []string `json:"allowed_methods" mapstructure:"allowed_methods"`
|
||||
AllowedHeaders []string `json:"allowed_headers" mapstructure:"allowed_headers"`
|
||||
|
@ -135,7 +135,7 @@ type Configuration struct {
|
|||
// if a client certificate has been revoked
|
||||
CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
|
||||
// CORS configuration
|
||||
Cors Cors `json:"cors" mapstructure:"cors"`
|
||||
Cors CorsConfig `json:"cors" mapstructure:"cors"`
|
||||
// Cache configuration
|
||||
Cache Cache `json:"cache" mapstructure:"cache"`
|
||||
}
|
||||
|
|
|
@ -347,7 +347,7 @@ func TestMain(m *testing.M) {
|
|||
ClientAuthType: 2,
|
||||
},
|
||||
}
|
||||
webDavConf.Cors = webdavd.Cors{
|
||||
webDavConf.Cors = webdavd.CorsConfig{
|
||||
Enabled: true,
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{
|
||||
|
|
Loading…
Reference in a new issue