s3tables: implement granular authorization and refine error responses
- Remove mandatory ACTION_ADMIN at the router level - Enforce granular permissions in bucket and namespace handlers - Prioritize AccountID in ExtractPrincipalFromContext for ARN matching - Distinguish between 404 (NoSuchBucket) and 500 (InternalError) in metadata lookups - Clean up unused imports in s3api_tables.go
This commit is contained in:
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||||
. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
||||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,9 +99,9 @@ func (s3a *S3ApiServer) registerS3TablesRoutes(router *mux.Router) {
|
|||||||
|
|
||||||
// Register the S3 Tables handler
|
// Register the S3 Tables handler
|
||||||
router.Methods(http.MethodPost).Path("/").MatcherFunc(s3TablesMatcher).
|
router.Methods(http.MethodPost).Path("/").MatcherFunc(s3TablesMatcher).
|
||||||
HandlerFunc(track(s3a.iam.Auth(func(w http.ResponseWriter, r *http.Request) {
|
HandlerFunc(track(func(w http.ResponseWriter, r *http.Request) {
|
||||||
s3TablesApi.S3TablesHandler(w, r)
|
s3TablesApi.S3TablesHandler(w, r)
|
||||||
}, ACTION_ADMIN), "S3Tables"))
|
}, "S3Tables"))
|
||||||
|
|
||||||
glog.V(1).Infof("S3 Tables API enabled")
|
glog.V(1).Infof("S3 Tables API enabled")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,11 @@ func (h *S3TablesHandler) handleGetTableBucket(w http.ResponseWriter, r *http.Re
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchBucket, fmt.Sprintf("table bucket %s not found", bucketName))
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
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 get table bucket: %v", err))
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +75,13 @@ func (h *S3TablesHandler) handleListTableBuckets(w http.ResponseWriter, r *http.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
principal := h.getPrincipalFromRequest(r)
|
||||||
|
if !CanListTableBuckets(principal, h.accountID) {
|
||||||
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to list table buckets")
|
||||||
|
return NewAuthError("ListTableBuckets", principal, "not authorized to list table buckets")
|
||||||
|
}
|
||||||
|
|
||||||
maxBuckets := req.MaxBuckets
|
maxBuckets := req.MaxBuckets
|
||||||
if maxBuckets <= 0 {
|
if maxBuckets <= 0 {
|
||||||
maxBuckets = 100
|
maxBuckets = 100
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ func (h *S3TablesHandler) handleCreateNamespace(w http.ResponseWriter, r *http.R
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
principal := h.getPrincipalFromRequest(r)
|
||||||
|
if !CanCreateNamespace(principal, h.accountID) {
|
||||||
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to create namespace")
|
||||||
|
return NewAuthError("CreateNamespace", principal, "not authorized to create namespace")
|
||||||
|
}
|
||||||
|
|
||||||
if req.TableBucketARN == "" {
|
if req.TableBucketARN == "" {
|
||||||
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
||||||
return fmt.Errorf("tableBucketARN is required")
|
return fmt.Errorf("tableBucketARN is required")
|
||||||
@@ -120,6 +127,13 @@ func (h *S3TablesHandler) handleGetNamespace(w http.ResponseWriter, r *http.Requ
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
principal := h.getPrincipalFromRequest(r)
|
||||||
|
if !CanGetNamespace(principal, h.accountID) {
|
||||||
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to get namespace details")
|
||||||
|
return NewAuthError("GetNamespace", principal, "not authorized to get namespace details")
|
||||||
|
}
|
||||||
|
|
||||||
if req.TableBucketARN == "" {
|
if req.TableBucketARN == "" {
|
||||||
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
||||||
return fmt.Errorf("tableBucketARN is required")
|
return fmt.Errorf("tableBucketARN is required")
|
||||||
@@ -172,6 +186,13 @@ func (h *S3TablesHandler) handleListNamespaces(w http.ResponseWriter, r *http.Re
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
principal := h.getPrincipalFromRequest(r)
|
||||||
|
if !CanListNamespaces(principal, h.accountID) {
|
||||||
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to list namespaces")
|
||||||
|
return NewAuthError("ListNamespaces", principal, "not authorized to list namespaces")
|
||||||
|
}
|
||||||
|
|
||||||
if req.TableBucketARN == "" {
|
if req.TableBucketARN == "" {
|
||||||
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
||||||
return fmt.Errorf("tableBucketARN is required")
|
return fmt.Errorf("tableBucketARN is required")
|
||||||
@@ -276,6 +297,13 @@ func (h *S3TablesHandler) handleDeleteNamespace(w http.ResponseWriter, r *http.R
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
principal := h.getPrincipalFromRequest(r)
|
||||||
|
if !CanDeleteNamespace(principal, h.accountID) {
|
||||||
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to delete namespace")
|
||||||
|
return NewAuthError("DeleteNamespace", principal, "not authorized to delete namespace")
|
||||||
|
}
|
||||||
|
|
||||||
if req.TableBucketARN == "" {
|
if req.TableBucketARN == "" {
|
||||||
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, "tableBucketARN is required")
|
||||||
return fmt.Errorf("tableBucketARN is required")
|
return fmt.Errorf("tableBucketARN is required")
|
||||||
|
|||||||
@@ -174,6 +174,9 @@ func ExtractPrincipalFromContext(contextID string) string {
|
|||||||
// Try to parse as ARN first
|
// Try to parse as ARN first
|
||||||
if strings.HasPrefix(contextID, "arn:") {
|
if strings.HasPrefix(contextID, "arn:") {
|
||||||
info := utils.ParsePrincipalARN(contextID)
|
info := utils.ParsePrincipalARN(contextID)
|
||||||
|
if info.AccountID != "" {
|
||||||
|
return info.AccountID
|
||||||
|
}
|
||||||
if info.RoleName != "" {
|
if info.RoleName != "" {
|
||||||
return info.RoleName
|
return info.RoleName
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user