Browse Source

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.
pull/8147/head
Chris Lu 4 days ago
parent
commit
2b2ff008cd
  1. 107
      weed/s3api/s3tables/handler_policy.go

107
weed/s3api/s3tables/handler_policy.go

@ -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 {
return json.Unmarshal(data, &existingTags)
if err != nil {
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)
} }
Loading…
Cancel
Save