[#257] Support flag to deny access if policy rules not found
All checks were successful
/ DCO (pull_request) Successful in 1m13s
/ Vulncheck (pull_request) Successful in 2m2s
/ Builds (1.20) (pull_request) Successful in 2m22s
/ Builds (1.21) (pull_request) Successful in 2m16s
/ Lint (pull_request) Successful in 3m26s
/ Tests (1.20) (pull_request) Successful in 2m21s
/ Tests (1.21) (pull_request) Successful in 1m37s

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2023-12-05 15:49:13 +03:00
parent ca15acf1d3
commit 43abf58068
8 changed files with 64 additions and 11 deletions

View file

@ -18,6 +18,7 @@ import (
type PolicySettings interface {
ResolveNamespaceAlias(ns string) string
PolicyDenyByDefault() bool
}
func PolicyCheck(storage engine.ChainRouter, settings PolicySettings, domains []string, log *zap.Logger) Func {
@ -27,7 +28,7 @@ func PolicyCheck(storage engine.ChainRouter, settings PolicySettings, domains []
st, err := policyCheck(storage, settings, domains, r)
if err == nil {
if st != chain.Allow && st != chain.NoRuleFound { // todo drop 'st != chain.NoRuleFound'
if st != chain.Allow && (st != chain.NoRuleFound || settings.PolicyDenyByDefault()) {
err = apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String()))
}
}

View file

@ -20,7 +20,9 @@ func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
return &middleware.Box{}, nil
}
type middlewareSettingsMock struct{}
type middlewareSettingsMock struct {
denyByDefault bool
}
func (r *middlewareSettingsMock) NamespaceHeader() string {
return FrostfsNamespaceHeader
@ -30,6 +32,10 @@ func (r *middlewareSettingsMock) ResolveNamespaceAlias(ns string) string {
return ns
}
func (r *middlewareSettingsMock) PolicyDenyByDefault() bool {
return r.denyByDefault
}
type handlerMock struct {
t *testing.T
}

View file

@ -26,6 +26,7 @@ import (
type routerMock struct {
router *chi.Mux
cfg Config
middlewareSettings *middlewareSettingsMock
}
func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@ -33,6 +34,8 @@ func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func prepareRouter(t *testing.T) *routerMock {
middlewareSettings := &middlewareSettingsMock{}
cfg := Config{
Throttle: middleware.ThrottleOpts{
Limit: 10,
@ -42,12 +45,13 @@ func prepareRouter(t *testing.T) *routerMock {
Center: &centerMock{},
Log: zaptest.NewLogger(t),
Metrics: &metrics.AppMetrics{},
MiddlewareSettings: &middlewareSettingsMock{},
MiddlewareSettings: middlewareSettings,
PolicyStorage: inmemory.NewInMemoryLocalOverrides(),
}
return &routerMock{
router: NewRouter(cfg),
cfg: cfg,
middlewareSettings: middlewareSettings,
}
}
@ -183,6 +187,24 @@ func TestPolicyChecker(t *testing.T) {
assertAPIError(t, w, apiErrors.ErrAccessDenied)
}
func TestDefaultBehaviorPolicyChecker(t *testing.T) {
chiRouter := prepareRouter(t)
bktName, objName := "bucket", "object"
target := fmt.Sprintf("/%s/%s", bktName, objName)
// check we can access bucket if rules not found
w, r := httptest.NewRecorder(), httptest.NewRequest(http.MethodPut, target, nil)
chiRouter.ServeHTTP(w, r)
resp := readResponse(t, w)
require.Equal(t, s3middleware.PutObjectOperation, resp.Method)
// check we cannot access if rules not found when settings is enabled
chiRouter.middlewareSettings.denyByDefault = true
w, r = httptest.NewRecorder(), httptest.NewRequest(http.MethodPut, target, nil)
chiRouter.ServeHTTP(w, r)
assertAPIError(t, w, apiErrors.ErrAccessDenied)
}
func readResponse(t *testing.T, w *httptest.ResponseRecorder) handlerResult {
var res handlerResult

View file

@ -102,6 +102,7 @@ type (
namespaceHeader string
defaultNamespaces []string
authorizedControlAPIKeys [][]byte
policyDenyByDefault bool
}
maxClientsConfig struct {
@ -212,6 +213,7 @@ func newAppSettings(log *Logger, v *viper.Viper, key *keys.PrivateKey) *appSetti
settings.setBufferMaxSizeForPut(v.GetUint64(cfgBufferMaxSizeForPut))
settings.setMD5Enabled(v.GetBool(cfgMD5Enabled))
settings.setAuthorizedControlAPIKeys(append(fetchAuthorizedKeys(log.logger, v), key.PublicKey()))
settings.setPolicyDenyByDefault(v.GetBool(cfgPolicyDenyByDefault))
return settings
}
@ -397,6 +399,18 @@ func (s *appSettings) ResolveNamespaceAlias(namespace string) string {
return namespace
}
func (s *appSettings) PolicyDenyByDefault() bool {
s.mu.RLock()
defer s.mu.RUnlock()
return s.policyDenyByDefault
}
func (s *appSettings) setPolicyDenyByDefault(policyDenyByDefault bool) {
s.mu.Lock()
s.policyDenyByDefault = policyDenyByDefault
s.mu.Unlock()
}
func (a *App) initAPI(ctx context.Context) {
a.initLayer(ctx)
a.initHandler()
@ -775,6 +789,7 @@ func (a *App) updateSettings() {
a.settings.setMD5Enabled(a.cfg.GetBool(cfgMD5Enabled))
a.settings.setDefaultNamespaces(a.cfg.GetStringSlice(cfgKludgeDefaultNamespaces))
a.settings.setAuthorizedControlAPIKeys(append(fetchAuthorizedKeys(a.log, a.cfg), a.key.PublicKey()))
a.settings.setPolicyDenyByDefault(a.cfg.GetBool(cfgPolicyDenyByDefault))
}
func (a *App) startServices() {

View file

@ -199,6 +199,7 @@ const ( // Settings.
// Enable return MD5 checksum in ETag.
cfgMD5Enabled = "features.md5.enabled"
cfgPolicyDenyByDefault = "features.policy.deny_by_default"
// FrostfsID.
cfgFrostfsIDEnabled = "frostfsid.enabled"

View file

@ -160,6 +160,8 @@ S3_GW_TRACING_EXPORTER="otlp_grpc"
S3_GW_RUNTIME_SOFT_MEMORY_LIMIT=1073741824
S3_GW_FEATURES_MD5_ENABLED=false
# Enable denying access for request that doesn't match any policy chain rules.
S3_GW_FEATURES_POLICY_DENY_BY_DEFAULT=false
# ReadTimeout is the maximum duration for reading the entire
# request, including the body. A zero or negative value means

View file

@ -189,6 +189,9 @@ runtime:
soft_memory_limit: 1gb
features:
policy:
# Enable denying access for request that doesn't match any policy chain rules.
deny_by_default: false
md5:
enabled: false

View file

@ -595,13 +595,16 @@ Contains parameters for enabling features.
```yaml
features:
policy:
deny_by_default: false
md5:
enabled: false
```
| Parameter | Type | SIGHUP reload | Default value | Description |
|---------------|--------|---------------|---------------|----------------------------------------------------------------|
|--------------------------|--------|---------------|---------------|------------------------------------------------------------------------------|
| `md5.enabled` | `bool` | yes | false | Flag to enable return MD5 checksum in ETag headers and fields. |
| `policy.deny_by_default` | `bool` | yes | false | Enable denying access for request that doesn't match any policy chain rules. |
# `web` section
Contains web server configuration parameters.