s3tables: improve resource resolution and error mapping for policies and tagging
Refactored resolveResourcePath to return resource type, enabling accurate NoSuchBucket vs NoSuchTable error codes. Added existence checks before deleting policies.
This commit is contained in:
@@ -13,7 +13,8 @@ import (
|
|||||||
func (h *S3TablesHandler) handlePutTableBucketPolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
func (h *S3TablesHandler) handlePutTableBucketPolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("PutTableBucketPolicy", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("PutTableBucketPolicy", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to put table bucket policy")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to put table bucket policy")
|
||||||
return NewAuthError("PutTableBucketPolicy", principal, "not authorized to put table bucket policy")
|
return NewAuthError("PutTableBucketPolicy", principal, "not authorized to put table bucket policy")
|
||||||
}
|
}
|
||||||
@@ -74,7 +75,8 @@ func (h *S3TablesHandler) handlePutTableBucketPolicy(w http.ResponseWriter, r *h
|
|||||||
func (h *S3TablesHandler) handleGetTableBucketPolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
func (h *S3TablesHandler) handleGetTableBucketPolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("GetTableBucketPolicy", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("GetTableBucketPolicy", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to get table bucket policy")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to get table bucket policy")
|
||||||
return NewAuthError("GetTableBucketPolicy", principal, "not authorized to get table bucket policy")
|
return NewAuthError("GetTableBucketPolicy", principal, "not authorized to get table bucket policy")
|
||||||
}
|
}
|
||||||
@@ -125,7 +127,8 @@ func (h *S3TablesHandler) handleGetTableBucketPolicy(w http.ResponseWriter, r *h
|
|||||||
func (h *S3TablesHandler) handleDeleteTableBucketPolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
func (h *S3TablesHandler) handleDeleteTableBucketPolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("DeleteTableBucketPolicy", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("DeleteTableBucketPolicy", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to delete table bucket policy")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to delete table bucket policy")
|
||||||
return NewAuthError("DeleteTableBucketPolicy", principal, "not authorized to delete table bucket policy")
|
return NewAuthError("DeleteTableBucketPolicy", principal, "not authorized to delete table bucket policy")
|
||||||
}
|
}
|
||||||
@@ -148,11 +151,26 @@ func (h *S3TablesHandler) handleDeleteTableBucketPolicy(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
bucketPath := getTableBucketPath(bucketName)
|
bucketPath := getTableBucketPath(bucketName)
|
||||||
|
|
||||||
|
// Check if bucket exists
|
||||||
|
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
_, err := h.getExtendedAttribute(r.Context(), client, bucketPath, ExtendedKeyMetadata)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, filer_pb.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 check table bucket: %v", err))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
return h.deleteExtendedAttribute(r.Context(), client, bucketPath, ExtendedKeyPolicy)
|
return h.deleteExtendedAttribute(r.Context(), client, bucketPath, ExtendedKeyPolicy)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, filer_pb.ErrNotFound) && !errors.Is(err, ErrAttributeNotFound) {
|
if err != nil && !errors.Is(err, ErrAttributeNotFound) {
|
||||||
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, "failed to delete table bucket policy")
|
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, "failed to delete table bucket policy")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -165,7 +183,8 @@ func (h *S3TablesHandler) handleDeleteTableBucketPolicy(w http.ResponseWriter, r
|
|||||||
func (h *S3TablesHandler) handlePutTablePolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
func (h *S3TablesHandler) handlePutTablePolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("PutTablePolicy", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("PutTablePolicy", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to put table policy")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to put table policy")
|
||||||
return NewAuthError("PutTablePolicy", principal, "not authorized to put table policy")
|
return NewAuthError("PutTablePolicy", principal, "not authorized to put table policy")
|
||||||
}
|
}
|
||||||
@@ -237,7 +256,8 @@ func (h *S3TablesHandler) handlePutTablePolicy(w http.ResponseWriter, r *http.Re
|
|||||||
func (h *S3TablesHandler) handleGetTablePolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
func (h *S3TablesHandler) handleGetTablePolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("GetTablePolicy", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("GetTablePolicy", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to get table policy")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to get table policy")
|
||||||
return NewAuthError("GetTablePolicy", principal, "not authorized to get table policy")
|
return NewAuthError("GetTablePolicy", principal, "not authorized to get table policy")
|
||||||
}
|
}
|
||||||
@@ -299,7 +319,8 @@ func (h *S3TablesHandler) handleGetTablePolicy(w http.ResponseWriter, r *http.Re
|
|||||||
func (h *S3TablesHandler) handleDeleteTablePolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
func (h *S3TablesHandler) handleDeleteTablePolicy(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("DeleteTablePolicy", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("DeleteTablePolicy", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to delete table policy")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to delete table policy")
|
||||||
return NewAuthError("DeleteTablePolicy", principal, "not authorized to delete table policy")
|
return NewAuthError("DeleteTablePolicy", principal, "not authorized to delete table policy")
|
||||||
}
|
}
|
||||||
@@ -333,11 +354,26 @@ func (h *S3TablesHandler) handleDeleteTablePolicy(w http.ResponseWriter, r *http
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tablePath := getTablePath(bucketName, namespaceName, tableName)
|
tablePath := getTablePath(bucketName, namespaceName, tableName)
|
||||||
|
|
||||||
|
// Check if table exists
|
||||||
|
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
|
_, err := h.getExtendedAttribute(r.Context(), client, tablePath, ExtendedKeyMetadata)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||||
|
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchTable, fmt.Sprintf("table %s not found", tableName))
|
||||||
|
} else {
|
||||||
|
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to check table: %v", err))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
return h.deleteExtendedAttribute(r.Context(), client, tablePath, ExtendedKeyPolicy)
|
return h.deleteExtendedAttribute(r.Context(), client, tablePath, ExtendedKeyPolicy)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, filer_pb.ErrNotFound) && !errors.Is(err, ErrAttributeNotFound) {
|
if err != nil && !errors.Is(err, ErrAttributeNotFound) {
|
||||||
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, "failed to delete table policy")
|
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, "failed to delete table policy")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -366,13 +402,14 @@ func (h *S3TablesHandler) handleTagResource(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CanManageTags(principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CanManageTags(principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to tag resource")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to tag resource")
|
||||||
return NewAuthError("TagResource", principal, "not authorized to tag resource")
|
return NewAuthError("TagResource", principal, "not authorized to tag resource")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse resource ARN to determine if it's a bucket or table
|
// Parse resource ARN to determine if it's a bucket or table
|
||||||
resourcePath, extendedKey, err := h.resolveResourcePath(req.ResourceARN)
|
resourcePath, extendedKey, rType, err := h.resolveResourcePath(req.ResourceARN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, err.Error())
|
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -382,14 +419,21 @@ func (h *S3TablesHandler) handleTagResource(w http.ResponseWriter, r *http.Reque
|
|||||||
existingTags := make(map[string]string)
|
existingTags := make(map[string]string)
|
||||||
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
data, err := h.getExtendedAttribute(r.Context(), client, resourcePath, extendedKey)
|
data, err := h.getExtendedAttribute(r.Context(), client, resourcePath, extendedKey)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
return json.Unmarshal(data, &existingTags)
|
if errors.Is(err, ErrAttributeNotFound) {
|
||||||
|
return nil // No existing tags, which is fine.
|
||||||
|
}
|
||||||
|
return err // Propagate other errors.
|
||||||
}
|
}
|
||||||
return nil
|
return json.Unmarshal(data, &existingTags)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, filer_pb.ErrNotFound) {
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||||
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchBucket, "resource not found")
|
errorCode := ErrCodeNoSuchBucket
|
||||||
|
if rType == ResourceTypeTable {
|
||||||
|
errorCode = ErrCodeNoSuchTable
|
||||||
|
}
|
||||||
|
h.writeError(w, http.StatusNotFound, errorCode, "resource not found")
|
||||||
} else {
|
} else {
|
||||||
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to read existing tags: %v", err))
|
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to read existing tags: %v", err))
|
||||||
}
|
}
|
||||||
@@ -424,7 +468,8 @@ func (h *S3TablesHandler) handleTagResource(w http.ResponseWriter, r *http.Reque
|
|||||||
func (h *S3TablesHandler) handleListTagsForResource(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
func (h *S3TablesHandler) handleListTagsForResource(w http.ResponseWriter, r *http.Request, filerClient FilerClient) error {
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("ListTagsForResource", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("ListTagsForResource", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to list tags for resource")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to list tags for resource")
|
||||||
return NewAuthError("ListTagsForResource", principal, "not authorized to list tags for resource")
|
return NewAuthError("ListTagsForResource", principal, "not authorized to list tags for resource")
|
||||||
}
|
}
|
||||||
@@ -440,7 +485,7 @@ func (h *S3TablesHandler) handleListTagsForResource(w http.ResponseWriter, r *ht
|
|||||||
return fmt.Errorf("resourceArn is required")
|
return fmt.Errorf("resourceArn is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
resourcePath, extendedKey, err := h.resolveResourcePath(req.ResourceARN)
|
resourcePath, extendedKey, rType, err := h.resolveResourcePath(req.ResourceARN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, err.Error())
|
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -450,14 +495,21 @@ func (h *S3TablesHandler) handleListTagsForResource(w http.ResponseWriter, r *ht
|
|||||||
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
err = filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
||||||
data, err := h.getExtendedAttribute(r.Context(), client, resourcePath, extendedKey)
|
data, err := h.getExtendedAttribute(r.Context(), client, resourcePath, extendedKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil // Return empty tags if not found
|
if errors.Is(err, ErrAttributeNotFound) {
|
||||||
|
return nil // No tags is not an error.
|
||||||
|
}
|
||||||
|
return err // Propagate other errors.
|
||||||
}
|
}
|
||||||
return json.Unmarshal(data, &tags)
|
return json.Unmarshal(data, &tags)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, filer_pb.ErrNotFound) {
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||||
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchBucket, "resource not found")
|
errorCode := ErrCodeNoSuchBucket
|
||||||
|
if rType == ResourceTypeTable {
|
||||||
|
errorCode = ErrCodeNoSuchTable
|
||||||
|
}
|
||||||
|
h.writeError(w, http.StatusNotFound, errorCode, "resource not found")
|
||||||
} else {
|
} else {
|
||||||
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to list tags: %v", err))
|
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, fmt.Sprintf("failed to list tags: %v", err))
|
||||||
}
|
}
|
||||||
@@ -492,12 +544,13 @@ func (h *S3TablesHandler) handleUntagResource(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
// Check permission
|
// Check permission
|
||||||
principal := h.getPrincipalFromRequest(r)
|
principal := h.getPrincipalFromRequest(r)
|
||||||
if !CheckPermission("UntagResource", principal, h.accountID) {
|
accountID := h.getAccountID(r)
|
||||||
|
if !CheckPermission("UntagResource", principal, accountID) {
|
||||||
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to untag resource")
|
h.writeError(w, http.StatusForbidden, ErrCodeAccessDenied, "not authorized to untag resource")
|
||||||
return NewAuthError("UntagResource", principal, "not authorized to untag resource")
|
return NewAuthError("UntagResource", principal, "not authorized to untag resource")
|
||||||
}
|
}
|
||||||
|
|
||||||
resourcePath, extendedKey, err := h.resolveResourcePath(req.ResourceARN)
|
resourcePath, extendedKey, rType, err := h.resolveResourcePath(req.ResourceARN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, err.Error())
|
h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, err.Error())
|
||||||
return err
|
return err
|
||||||
@@ -518,7 +571,11 @@ func (h *S3TablesHandler) handleUntagResource(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, filer_pb.ErrNotFound) {
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
||||||
h.writeError(w, http.StatusNotFound, ErrCodeNoSuchBucket, "resource not found")
|
errorCode := ErrCodeNoSuchBucket
|
||||||
|
if rType == ResourceTypeTable {
|
||||||
|
errorCode = ErrCodeNoSuchTable
|
||||||
|
}
|
||||||
|
h.writeError(w, http.StatusNotFound, errorCode, "resource not found")
|
||||||
} else {
|
} else {
|
||||||
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, "failed to read existing tags")
|
h.writeError(w, http.StatusInternalServerError, ErrCodeInternalError, "failed to read existing tags")
|
||||||
}
|
}
|
||||||
@@ -550,18 +607,18 @@ func (h *S3TablesHandler) handleUntagResource(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolveResourcePath determines the resource path and extended attribute key from a resource ARN
|
// resolveResourcePath determines the resource path and extended attribute key from a resource ARN
|
||||||
func (h *S3TablesHandler) resolveResourcePath(resourceARN string) (path string, key string, err error) {
|
func (h *S3TablesHandler) resolveResourcePath(resourceARN string) (path string, key string, rType ResourceType, err error) {
|
||||||
// Try parsing as table ARN first
|
// Try parsing as table ARN first
|
||||||
bucketName, namespace, tableName, err := parseTableFromARN(resourceARN)
|
bucketName, namespace, tableName, err := parseTableFromARN(resourceARN)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return getTablePath(bucketName, namespace, tableName), ExtendedKeyTags, nil
|
return getTablePath(bucketName, namespace, tableName), ExtendedKeyTags, ResourceTypeTable, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try parsing as bucket ARN
|
// Try parsing as bucket ARN
|
||||||
bucketName, err = parseBucketNameFromARN(resourceARN)
|
bucketName, err = parseBucketNameFromARN(resourceARN)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return getTableBucketPath(bucketName), ExtendedKeyTags, nil
|
return getTableBucketPath(bucketName), ExtendedKeyTags, ResourceTypeBucket, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", "", fmt.Errorf("invalid resource ARN: %s", resourceARN)
|
return "", "", "", fmt.Errorf("invalid resource ARN: %s", resourceARN)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user