s3tables: authorize CreateNamespace before revealing a missing bucket

A bucket that is absent or not a table bucket used to answer 404 before the permission check ran, while a denied existing bucket answered 403. An unauthorized caller now gets the same denial unless it may learn the name is absent (admin, trusted handler, identity grant).
This commit is contained in:
Chris Lu
2026-10-21 13:00:18 -07:00
parent 295303fc1a
commit 6ed29f8c04
2 changed files with 20 additions and 0 deletions
+14
View File
@@ -459,6 +459,20 @@ func isAuthError(err error) bool {
return errors.As(err, &authErr) || errors.Is(err, ErrAccessDenied)
}
// mayLearnAbsence reports whether the caller may be shown that an operation's
// target is absent rather than the operation's denial: the admin, a trusted
// handler, or a grant the request itself carries (an identity-level action
// grant, or the open default-allow context).
func (h *S3TablesHandler) mayLearnAbsence(r *http.Request, operation, bucketName string) bool {
if h.getAccountID(r) == s3_constants.AccountAdminId || h.defaultAllowFor(r) {
return true
}
return hasIdentityPermission(operation, &PolicyContext{
TableBucketName: bucketName,
IdentityActions: getIdentityActions(r),
})
}
func (h *S3TablesHandler) readTags(ctx context.Context, client filer_pb.SeaweedFilerClient, path string) (map[string]string, error) {
data, err := h.getExtendedAttribute(ctx, client, path, ExtendedKeyTags)
if err != nil {
+6
View File
@@ -140,6 +140,12 @@ func (h *S3TablesHandler) handleCreateNamespace(w http.ResponseWriter, r *http.R
return ErrAccessDenied
}
if errors.Is(err, filer_pb.ErrNotFound) {
// An absent name and a denied one must read alike; the caller is
// told the bucket is missing only when it may learn that.
if !h.mayLearnAbsence(r, "CreateNamespace", bucketName) {
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to create namespace in this bucket")
return ErrAccessDenied
}
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchBucket, fmt.Sprintf("table bucket %s not found", bucketName))
} else {
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to check table bucket: %v", err))