allow to disable REST API

Fixes #987

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-09-22 17:27:00 +02:00
parent 6c7b3ac5bb
commit 7ae9303c99
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
8 changed files with 239 additions and 177 deletions

View file

@ -260,6 +260,7 @@ The configuration file contains the following sections:
- `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: blank.
- `enable_web_admin`, boolean. Set to `false` to disable the built-in web admin for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web admin interface. Default `true`.
- `enable_web_client`, boolean. Set to `false` to disable the built-in web client for this binding. You also need to define `templates_path` and `static_files_path` to use the built-in web client interface. Default `true`.
- `enable_rest_api`, boolean. Set to `false` to disable REST API. Default `true`.
- `enabled_login_methods`, integer. Defines the login methods available for the WebAdmin and WebClient UIs. `0` means any configured method: username/password login form and OIDC, if enabled. `1` means OIDC for the WebAdmin UI. `2` means OIDC for the WebClient UI. `4` means login form for the WebAdmin UI. `8` means login form for the WebClient UI. You can combine the values. For example `3` means that you can only login using OIDC on both WebClient and WebAdmin UI. Default: `0`.
- `enable_https`, boolean. Set to `true` and provide both a certificate and a key file to enable HTTPS connection for this binding. Default `false`.
- `certificate_file`, string. Binding specific TLS certificate. This can be an absolute path or a path relative to the config dir.

View file

@ -20,6 +20,8 @@ If you define multiple bindings, each binding will sign JWT tokens with a differ
If, instead, you want to use a persistent signing key for JWT tokens, you can define a signing passphrase via configuration file or environment variable.
REST API can be disabled within the `httpd` configuration via the `enable_rest_api` key.
You can create other administrator and assign them the following permissions:
- add users
@ -35,8 +37,11 @@ You can create other administrator and assign them the following permissions:
- manage API keys
- manage system
- manage admins
- manage groups
- manage data retention
- manage metadata
- view events
- manage event rules
You can also restrict administrator access based on the source IP address. If you are running SFTPGo behind a reverse proxy you need to allow both the proxy IP address and the real client IP.

View file

@ -100,6 +100,7 @@ var (
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: true,
EnableRESTAPI: true,
EnabledLoginMethods: 0,
EnableHTTPS: false,
CertificateFile: "",
@ -1685,6 +1686,12 @@ func getHTTPDBindingFromEnv(idx int) { //nolint:gocyclo
isSet = true
}
enableRESTAPI, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLE_REST_API", idx))
if ok {
binding.EnableRESTAPI = enableRESTAPI
isSet = true
}
enabledLoginMethods, ok := lookupIntFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__ENABLED_LOGIN_METHODS", idx))
if ok {
binding.EnabledLoginMethods = int(enabledLoginMethods)

View file

@ -1019,6 +1019,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_REST_API", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLED_LOGIN_METHODS", "3")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__RENDER_OPENAPI", "0")
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_HTTPS", "1 ")
@ -1088,6 +1089,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__MIN_TLS_VERSION")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_CLIENT")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_REST_API")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ENABLED_LOGIN_METHODS")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__RENDER_OPENAPI")
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__CLIENT_AUTH_TYPE")
@ -1149,6 +1151,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Equal(t, 12, bindings[0].MinTLSVersion)
require.True(t, bindings[0].EnableWebAdmin)
require.True(t, bindings[0].EnableWebClient)
require.True(t, bindings[0].EnableRESTAPI)
require.Equal(t, 0, bindings[0].EnabledLoginMethods)
require.True(t, bindings[0].RenderOpenAPI)
require.Len(t, bindings[0].TLSCipherSuites, 1)
@ -1165,6 +1168,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Equal(t, 12, bindings[0].MinTLSVersion)
require.True(t, bindings[1].EnableWebAdmin)
require.True(t, bindings[1].EnableWebClient)
require.True(t, bindings[1].EnableRESTAPI)
require.Equal(t, 0, bindings[1].EnabledLoginMethods)
require.True(t, bindings[1].RenderOpenAPI)
require.Nil(t, bindings[1].TLSCipherSuites)
@ -1182,6 +1186,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
require.Equal(t, 13, bindings[2].MinTLSVersion)
require.False(t, bindings[2].EnableWebAdmin)
require.False(t, bindings[2].EnableWebClient)
require.False(t, bindings[2].EnableRESTAPI)
require.Equal(t, 3, bindings[2].EnabledLoginMethods)
require.False(t, bindings[2].RenderOpenAPI)
require.Equal(t, 1, bindings[2].ClientAuthType)

View file

@ -410,6 +410,8 @@ type Binding struct {
// Enable the built-in client interface.
// You have to define TemplatesPath and StaticFilesPath for this to work
EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"`
// Enable REST API
EnableRESTAPI bool `json:"enable_rest_api" mapstructure:"enable_rest_api"`
// Defines the login methods available for the WebAdmin and WebClient UIs:
//
// - 0 means any configured method: username/password login form and OIDC, if enabled
@ -522,6 +524,9 @@ func (b *Binding) GetAddress() string {
// IsValid returns true if the binding is valid
func (b *Binding) IsValid() bool {
if !b.EnableRESTAPI && !b.EnableWebAdmin && !b.EnableWebClient {
return false
}
if b.Port > 0 {
return true
}

View file

@ -309,6 +309,8 @@ func TestShouldBind(t *testing.T) {
},
},
}
require.False(t, c.ShouldBind())
c.Bindings[0].EnableRESTAPI = true
require.True(t, c.ShouldBind())
c.Bindings[0].Port = 0
@ -833,6 +835,7 @@ func TestCSRFToken(t *testing.T) {
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: true,
EnableRESTAPI: true,
RenderOpenAPI: true,
})
fn := verifyCSRFHeader(r)
@ -1080,6 +1083,7 @@ func TestAPIKeyAuthForbidden(t *testing.T) {
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: true,
EnableRESTAPI: true,
RenderOpenAPI: true,
})
fn := forbidAPIKeyAuthentication(r)
@ -1104,6 +1108,7 @@ func TestJWTTokenValidation(t *testing.T) {
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: true,
EnableRESTAPI: true,
RenderOpenAPI: true,
},
}
@ -1648,6 +1653,7 @@ func TestProxyHeaders(t *testing.T) {
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: false,
EnableRESTAPI: true,
ProxyAllowed: []string{testIP, "10.8.0.0/30"},
ClientIPProxyHeader: "x-forwarded-for",
}
@ -1739,6 +1745,7 @@ func TestRecoverer(t *testing.T) {
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: false,
EnableRESTAPI: true,
}
server := newHttpdServer(b, "../static", "", CorsConfig{}, "../openapi")
server.initializeRouter()
@ -1859,6 +1866,7 @@ func TestWebAdminRedirect(t *testing.T) {
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: false,
EnableRESTAPI: true,
}
server := newHttpdServer(b, "../static", "", CorsConfig{}, "../openapi")
server.initializeRouter()
@ -2323,16 +2331,19 @@ func TestLoginLinks(t *testing.T) {
b := Binding{
EnableWebAdmin: true,
EnableWebClient: false,
EnableRESTAPI: true,
}
assert.False(t, b.showClientLoginURL())
b = Binding{
EnableWebAdmin: false,
EnableWebClient: true,
EnableRESTAPI: true,
}
assert.False(t, b.showAdminLoginURL())
b = Binding{
EnableWebAdmin: true,
EnableWebClient: true,
EnableRESTAPI: true,
}
assert.True(t, b.showAdminLoginURL())
assert.True(t, b.showClientLoginURL())
@ -2489,6 +2500,7 @@ func TestSecureMiddlewareIntegration(t *testing.T) {
},
enableWebAdmin: true,
enableWebClient: true,
enableRESTAPI: true,
}
server.binding.Security.updateProxyHeaders()
err := server.binding.parseAllowedProxy()
@ -2560,6 +2572,27 @@ func TestGetCompressedFileName(t *testing.T) {
require.Equal(t, fmt.Sprintf("%s-file1.zip", username), res)
}
func TestRESTAPIDisabled(t *testing.T) {
server := httpdServer{
enableWebAdmin: true,
enableWebClient: true,
enableRESTAPI: false,
}
server.initializeRouter()
assert.False(t, server.enableRESTAPI)
rr := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodGet, healthzPath, nil)
assert.NoError(t, err)
server.router.ServeHTTP(rr, r)
assert.Equal(t, http.StatusOK, rr.Code)
rr = httptest.NewRecorder()
r, err = http.NewRequest(http.MethodGet, tokenPath, nil)
assert.NoError(t, err)
server.router.ServeHTTP(rr, r)
assert.Equal(t, http.StatusNotFound, rr.Code)
}
func TestWebAdminSetupWithInstallCode(t *testing.T) {
installationCode = "1234"
// delete all the admins
@ -2580,6 +2613,7 @@ func TestWebAdminSetupWithInstallCode(t *testing.T) {
server := httpdServer{
enableWebAdmin: true,
enableWebClient: true,
enableRESTAPI: true,
}
server.initializeRouter()

View file

@ -57,6 +57,7 @@ type httpdServer struct {
openAPIPath string
enableWebAdmin bool
enableWebClient bool
enableRESTAPI bool
renderOpenAPI bool
isShared int
router *chi.Mux
@ -77,6 +78,7 @@ func newHttpdServer(b Binding, staticFilesPath, signingPassphrase string, cors C
openAPIPath: openAPIPath,
enableWebAdmin: b.EnableWebAdmin,
enableWebClient: b.EnableWebClient,
enableRESTAPI: b.EnableRESTAPI,
renderOpenAPI: b.RenderOpenAPI,
signingPassphrase: signingPassphrase,
cors: cors,
@ -1178,6 +1180,7 @@ func (s *httpdServer) initializeRouter() {
render.PlainText(w, r, "User-agent: *\nDisallow: /")
})
if s.enableRESTAPI {
// share API exposed to external users
s.router.Get(sharesPath+"/{id}", s.downloadFromShare)
s.router.Post(sharesPath+"/{id}", s.uploadFilesToShare)
@ -1360,6 +1363,7 @@ func (s *httpdServer) initializeRouter() {
serveStaticDir(router, webOpenAPIPath, s.openAPIPath)
})
}
}
if s.enableWebAdmin || s.enableWebClient {
s.router.Group(func(router chi.Router) {

View file

@ -245,6 +245,7 @@
"address": "",
"enable_web_admin": true,
"enable_web_client": true,
"enable_rest_api": true,
"enabled_login_methods": 0,
"enable_https": false,
"certificate_file": "",