mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-22 22:11:39 +02:00
* s3tables: only the admin repairs a table bucket that has no owner metadata CreateNamespace wrote bucket metadata naming the caller as owner before any permission check ran, so an ordinary caller that found the marker without metadata was written in as the bucket's owner and passed every check after. A bucket in that state is answered with the operation's own denial; the admin or a trusted internal handler may still repair it. * s3tables: repair the marker-only bucket under the entry lock A creator finishing its own create between the missing-metadata check and the repair write would have its owner overwritten. The mutation now adopts whatever metadata is there and writes only while it is still absent. * s3tables: gate the repair on the Admin action, not an operation grant hasAdminAction also answers s3tables:*, so a non-admin identity holding the wildcard operation grant could pass the repair check and write itself in as the bucket's owner.
89 lines
3.6 KiB
Go
89 lines
3.6 KiB
Go
package s3tables
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables/s3tablestest"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// A table bucket with the marker but no metadata is damaged, not ownerless for
|
|
// the taking: an ordinary caller is answered with the operation's own denial,
|
|
// and the admin or a trusted handler may repair it.
|
|
func TestCreateNamespaceOnAnOwnerlessBucket(t *testing.T) {
|
|
const bucket = "broken"
|
|
plant := func(fs *s3tablestest.MemFiler) {
|
|
fs.Put(TablesPath, bucket, map[string][]byte{ExtendedKeyTableBucket: []byte("true")})
|
|
}
|
|
arn, err := BuildBucketARN(DefaultRegion, DefaultAccountID, bucket)
|
|
require.NoError(t, err)
|
|
request := &CreateNamespaceRequest{TableBucketARN: arn, Namespace: []string{"ns"}}
|
|
|
|
t.Run("an outsider gets the denial and writes nothing", func(t *testing.T) {
|
|
fs := s3tablestest.Start(t)
|
|
plant(fs)
|
|
m := NewManager()
|
|
body, err := json.Marshal(request)
|
|
require.NoError(t, err)
|
|
identity := &testIdentity{Name: "outsider", Account: &testIdentityAccount{Id: "outsider"}, Actions: []string{"Read"}}
|
|
req, err := newManagerRequest(s3_constants.SetIdentityInContext(context.Background(), identity), "CreateNamespace", body, "outsider")
|
|
require.NoError(t, err)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
m.handler.HandleRequest(recorder, req, NewManagerClient(fs.Client))
|
|
assert.Equal(t, http.StatusForbidden, recorder.Code, recorder.Body.String())
|
|
entry := fs.Get(TablesPath, bucket)
|
|
require.NotNil(t, entry)
|
|
assert.Empty(t, entry.Extended[ExtendedKeyMetadata], "the denied caller wrote itself in as the bucket owner")
|
|
assert.Nil(t, fs.Get(GetTableBucketPath(bucket), "ns"))
|
|
})
|
|
|
|
t.Run("a trusted handler repairs it and owns it", func(t *testing.T) {
|
|
fs := s3tablestest.Start(t)
|
|
plant(fs)
|
|
m := NewManager()
|
|
m.SetTrusted(true)
|
|
body, err := json.Marshal(request)
|
|
require.NoError(t, err)
|
|
identity := &testIdentity{Name: "svc", Account: &testIdentityAccount{Id: "svc"}, Actions: []string{"Read"}}
|
|
req, err := newManagerRequest(s3_constants.SetIdentityInContext(context.Background(), identity), "CreateNamespace", body, "svc")
|
|
require.NoError(t, err)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
m.handler.HandleRequest(recorder, req, NewManagerClient(fs.Client))
|
|
assert.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
|
|
entry := fs.Get(TablesPath, bucket)
|
|
require.NotNil(t, entry)
|
|
var metadata tableBucketMetadata
|
|
require.NoError(t, json.Unmarshal(entry.Extended[ExtendedKeyMetadata], &metadata))
|
|
assert.Equal(t, "svc", metadata.OwnerAccountID)
|
|
require.NotNil(t, fs.Get(GetTableBucketPath(bucket), "ns"))
|
|
})
|
|
|
|
t.Run("an untrusted admin repairs it too", func(t *testing.T) {
|
|
fs := s3tablestest.Start(t)
|
|
plant(fs)
|
|
m := NewManager()
|
|
body, err := json.Marshal(request)
|
|
require.NoError(t, err)
|
|
identity := &testIdentity{Name: "admin", Account: &testIdentityAccount{Id: s3_constants.AccountAdminId}, Actions: []string{"Admin"}}
|
|
req, err := newManagerRequest(s3_constants.SetIdentityInContext(context.Background(), identity), "CreateNamespace", body, "admin")
|
|
require.NoError(t, err)
|
|
|
|
recorder := httptest.NewRecorder()
|
|
m.handler.HandleRequest(recorder, req, NewManagerClient(fs.Client))
|
|
assert.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
|
|
entry := fs.Get(TablesPath, bucket)
|
|
require.NotNil(t, entry)
|
|
var metadata tableBucketMetadata
|
|
require.NoError(t, json.Unmarshal(entry.Extended[ExtendedKeyMetadata], &metadata))
|
|
assert.Equal(t, s3_constants.AccountAdminId, metadata.OwnerAccountID)
|
|
})
|
|
}
|