s3tables: harden namespace validation and correct ARN parsing
- Prohibit path traversal (".", "..") and "/" in namespaces
- Restrict namespace characters to [a-z0-9_] for consistency
- Switch to url.PathUnescape for correct decoding of ARN path components
- Align ARN parsing regex with single-segment namespace validation
This commit is contained in:
@@ -25,15 +25,15 @@ func parseBucketNameFromARN(arn string) (string, error) {
|
|||||||
// parseTableFromARN extracts bucket name, namespace, and table name from ARN
|
// parseTableFromARN extracts bucket name, namespace, and table name from ARN
|
||||||
// ARN format: arn:aws:s3tables:{region}:{account}:bucket/{bucket-name}/table/{namespace}/{table-name}
|
// ARN format: arn:aws:s3tables:{region}:{account}:bucket/{bucket-name}/table/{namespace}/{table-name}
|
||||||
func parseTableFromARN(arn string) (bucketName, namespace, tableName string, err error) {
|
func parseTableFromARN(arn string) (bucketName, namespace, tableName string, err error) {
|
||||||
// Updated regex to capture multi-segment namespaces containing slashes
|
// Updated regex to align with namespace validation (single-segment)
|
||||||
pattern := regexp.MustCompile(`^arn:aws:s3tables:[^:]*:[^:]*:bucket/([a-z0-9_-]+)/table/([^/]+(?:/[^/]+)*)/([a-z0-9_]+)$`)
|
pattern := regexp.MustCompile(`^arn:aws:s3tables:[^:]*:[^:]*:bucket/([a-z0-9_-]+)/table/([^/]+)/([a-z0-9_]+)$`)
|
||||||
matches := pattern.FindStringSubmatch(arn)
|
matches := pattern.FindStringSubmatch(arn)
|
||||||
if len(matches) != 4 {
|
if len(matches) != 4 {
|
||||||
return "", "", "", fmt.Errorf("invalid table ARN: %s", arn)
|
return "", "", "", fmt.Errorf("invalid table ARN: %s", arn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL decode the namespace
|
// URL decode the namespace from the ARN path component
|
||||||
namespaceUnescaped, err := url.QueryUnescape(matches[2])
|
namespaceUnescaped, err := url.PathUnescape(matches[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", fmt.Errorf("invalid namespace encoding in ARN: %v", err)
|
return "", "", "", fmt.Errorf("invalid namespace encoding in ARN: %v", err)
|
||||||
}
|
}
|
||||||
@@ -120,6 +120,23 @@ func validateNamespace(namespace []string) (string, error) {
|
|||||||
if len(name) < 1 || len(name) > 255 {
|
if len(name) < 1 || len(name) > 255 {
|
||||||
return "", fmt.Errorf("namespace name must be between 1 and 255 characters")
|
return "", fmt.Errorf("namespace name must be between 1 and 255 characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent path traversal and multi-segment paths within a single namespace element.
|
||||||
|
if name == "." || name == ".." {
|
||||||
|
return "", fmt.Errorf("namespace name cannot be '.' or '..'")
|
||||||
|
}
|
||||||
|
if strings.Contains(name, "/") {
|
||||||
|
return "", fmt.Errorf("namespace name cannot contain '/'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce allowed character set consistent with table naming.
|
||||||
|
for _, ch := range name {
|
||||||
|
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("invalid namespace name: only 'a-z', '0-9', and '_' are allowed")
|
||||||
|
}
|
||||||
|
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user