s3api: reject versionId requests on buckets that never had versioning

GET, HEAD and DELETE carrying a non-empty versionId on an unversioned
bucket now fail with InvalidArgument instead of being answered as a
plain object request.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Chris LuandDevin committed 2026-10-06 09:16:37 -07:00
1 parent 1afa3da1ac
commit b773170bd3
3 files changed
+40 -6

No files matched your search

+18
View File
@@ -646,6 +646,24 @@ func (s3a *S3ApiServer) isVersioningConfigured(bucket string) (bool, error) {
return config.Versioning != "" || config.ObjectLockConfig != nil, nil
}
// checkVersionIdConfigured reports ErrInvalidArgument when versionId names a
// specific version on a bucket that has never had versioning configured. The
// implicit "null" version of pre-versioning objects stays addressable.
func (s3a *S3ApiServer) checkVersionIdConfigured(bucket, versionId string) s3err.ErrorCode {
if versionId == "" || versionId == "null" {
return s3err.ErrNone
}
configured, err := s3a.isVersioningConfigured(bucket)
if err != nil {
// Missing buckets and lookup failures are answered by the handler path.
return s3err.ErrNone
}
if !configured {
return s3err.ErrInvalidArgument
}
return s3err.ErrNone
}
// isObjectLockEnabled checks if Object Lock is enabled for a bucket (with caching)
func (s3a *S3ApiServer) isObjectLockEnabled(bucket string) (bool, error) {
config, errCode := s3a.getBucketConfig(bucket)
+17 -6
View File
@@ -680,6 +680,15 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
bucket, object, totalTime, conditionalHeadersTime, versioningCheckTime, entryFetchTime, streamTime)
}()
// A non-null versionId only has meaning once versioning has been
// configured; reject it before directory or conditional handling can
// answer the request as a plain object.
versionId := r.URL.Query().Get("versionId")
if errCode := s3a.checkVersionIdConfigured(bucket, versionId); errCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, errCode)
return
}
// Handle directory objects with shared logic
if s3a.handleDirectoryObjectRequest(w, r, bucket, object, "GetObjectHandler") {
return // Directory object request was handled
@@ -693,9 +702,6 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request)
return
}
// Check for specific version ID in query parameters
versionId := r.URL.Query().Get("versionId")
var (
entry *filer_pb.Entry // Declare entry at function scope for SSE processing
targetVersionId string
@@ -2359,6 +2365,14 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
return // SOSAPI request was handled
}
// Reject named versions before directory or conditional handling can
// answer the request as a plain object.
versionId := r.URL.Query().Get("versionId")
if errCode := s3a.checkVersionIdConfigured(bucket, versionId); errCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, errCode)
return
}
// Handle directory objects with shared logic
if s3a.handleDirectoryObjectRequest(w, r, bucket, object, "HeadObjectHandler") {
return // Directory object request was handled
@@ -2370,9 +2384,6 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request
return
}
// Check for specific version ID in query parameters
versionId := r.URL.Query().Get("versionId")
var (
entry *filer_pb.Entry // Declare entry at function scope for SSE processing
targetVersionId string
@@ -232,6 +232,11 @@ func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Reque
versioningConfigured := (versioningState != "")
if errCode := s3a.checkVersionIdConfigured(bucket, versionId); errCode != s3err.ErrNone {
s3err.WriteErrorResponse(w, r, errCode)
return
}
var auditLog *s3err.AccessLog
if s3err.Logger != nil {
auditLog = s3err.GetAccessLog(r, http.StatusNoContent, s3err.ErrNone)