Files
seaweedfs/weed/s3api/auth_config_default_deny_test.go
T
Chris Lu 368b2035b2 s3: deny anonymous access when the identity config loads no identities (#10954)
* s3: deny anonymous requests when the identity config loads no identities

Naming a config file is the operator asking for authentication. A file that
yields no identity - an unpopulated secret mount, or a mistyped top-level key
the proto parser silently drops - left the gateway open to every anonymous
caller: ListBuckets returned 200, and anonymous PUT could create buckets and
write objects.

* s3: name the unknown top-level keys in an identity config

The proto parser discards what it does not recognise, so a mistyped
"identites" loads as an empty config. Naming the dropped keys at startup turns
the resulting lockout into a one-line diagnosis.

* s3: isolate the auth-enforcement tests from AWS environment credentials

* s3: use a singular "identity" as the unrecognised-key example

Codespell rejects the misspelling the example used.

* s3: cover the empty identity config alongside the unrecognised key

* s3: cover a config file whose body is an empty object
2026-08-25 15:31:32 -07:00

69 lines
2.7 KiB
Go

package s3api
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/stretchr/testify/assert"
)
// A config file the proto parser reads as empty - an unpopulated secret mount,
// a singular "identity" - used to leave the gateway serving every anonymous
// request, including ListBuckets and bucket creation.
func TestConfigWithoutIdentitiesDeniesAnonymous(t *testing.T) {
for name, config := range map[string]string{
"no identities key": `{}`,
"empty": `{"identities":[]}`,
"unrecognised key": `{"identity":[{"name":"admin","credentials":[{"accessKey":"adminkey","secretKey":"adminsecret"}],"actions":["Admin"]}]}`,
} {
t.Run(name, func(t *testing.T) {
resetMemoryStore()
clearEnvironmentVariableCredentials(t)
path := writeTempIamConfig(t, config)
iam := NewIdentityAccessManagementWithStore(&S3ApiServerOption{Config: path}, nil, "memory")
assert.True(t, iam.isEnabled(), "naming a config file asks for authentication, even if it yields no identity")
handlerCalled := false
handler := iam.Auth(func(w http.ResponseWriter, r *http.Request) {
handlerCalled = true
}, s3_constants.ACTION_LIST)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
assert.False(t, handlerCalled, "ListBuckets must not run for an anonymous caller")
assert.Equal(t, http.StatusForbidden, recorder.Code)
})
}
}
// `weed mini` and `docker run seaweedfs` name no config file and stay open.
func TestNoConfigKeepsAnonymousAllowed(t *testing.T) {
resetMemoryStore()
clearEnvironmentVariableCredentials(t)
iam := NewIdentityAccessManagementWithStore(&S3ApiServerOption{}, nil, "memory")
assert.False(t, iam.isEnabled(), "auth must stay off when no config file and no identities are configured")
}
// AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY register an admin identity of
// their own, which would decide isAuthEnabled before the config file does.
func clearEnvironmentVariableCredentials(t *testing.T) {
t.Setenv("AWS_ACCESS_KEY_ID", "")
t.Setenv("AWS_SECRET_ACCESS_KEY", "")
}
// The proto parser drops what it does not recognise, so a typo has to be named
// at startup or the resulting lockout has no visible cause.
func TestUnknownS3ConfigKeys(t *testing.T) {
assert.Equal(t, []string{"identity"}, unknownS3ConfigKeys([]byte(`{"identity":[],"accounts":[]}`)))
assert.Empty(t, unknownS3ConfigKeys([]byte(`{"identities":[],"service_accounts":[],"serviceAccounts":[],"policies":[],"groups":[]}`)))
assert.Empty(t, unknownS3ConfigKeys([]byte(`{"kms":{},"sts":{},"policy":{},"providers":[],"roles":[]}`)), "sections owned by other subsystems are not typos")
assert.Empty(t, unknownS3ConfigKeys([]byte(`not json`)))
}