* chore: remove unreachable dead code across the codebase Remove ~50,000 lines of unreachable code identified by static analysis. Major removals: - weed/filer/redis_lua: entire unused Redis Lua filer store implementation - weed/wdclient/net2, resource_pool: unused connection/resource pool packages - weed/plugin/worker/lifecycle: unused lifecycle plugin worker - weed/s3api: unused S3 policy templates, presigned URL IAM, streaming copy, multipart IAM, key rotation, and various SSE helper functions - weed/mq/kafka: unused partition mapping, compression, schema, and protocol functions - weed/mq/offset: unused SQL storage and migration code - weed/worker: unused registry, task, and monitoring functions - weed/query: unused SQL engine, parquet scanner, and type functions - weed/shell: unused EC proportional rebalance functions - weed/storage/erasure_coding/distribution: unused distribution analysis functions - Individual unreachable functions removed from 150+ files across admin, credential, filer, iam, kms, mount, mq, operation, pb, s3api, server, shell, storage, topology, and util packages * fix(s3): reset shared memory store in IAM test to prevent flaky failure TestLoadIAMManagerFromConfig_EmptyConfigWithFallbackKey was flaky because the MemoryStore credential backend is a singleton registered via init(). Earlier tests that create anonymous identities pollute the shared store, causing LookupAnonymous() to unexpectedly return true. Fix by calling Reset() on the memory store before the test runs. * style: run gofmt on changed files * fix: restore KMS functions used by integration tests * fix(plugin): prevent panic on send to closed worker session channel The Plugin.sendToWorker method could panic with "send on closed channel" when a worker disconnected while a message was being sent. The race was between streamSession.close() closing the outgoing channel and sendToWorker writing to it concurrently. Add a done channel to streamSession that is closed before the outgoing channel, and check it in sendToWorker's select to safely detect closed sessions without panicking.
368 lines
12 KiB
Go
368 lines
12 KiB
Go
package s3api
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_objectlock"
|
|
)
|
|
|
|
// ObjectLockUtils provides shared utilities for Object Lock configuration
|
|
// These functions are used by both Admin UI and S3 API handlers to ensure consistency
|
|
|
|
// VersioningUtils provides shared utilities for bucket versioning configuration
|
|
// These functions ensure Admin UI and S3 API use the same versioning keys
|
|
|
|
// StoreVersioningInExtended stores versioning configuration in entry extended attributes
|
|
func StoreVersioningInExtended(entry *filer_pb.Entry, enabled bool) error {
|
|
if entry.Extended == nil {
|
|
entry.Extended = make(map[string][]byte)
|
|
}
|
|
|
|
if enabled {
|
|
entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningEnabled)
|
|
} else {
|
|
// Don't set the header when versioning is not enabled
|
|
delete(entry.Extended, s3_constants.ExtVersioningKey)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetVersioningStatus returns the versioning status as a string: "", "Enabled", or "Suspended"
|
|
// Empty string means versioning was never enabled
|
|
func GetVersioningStatus(entry *filer_pb.Entry) string {
|
|
if entry == nil || entry.Extended == nil {
|
|
return "" // Never enabled
|
|
}
|
|
|
|
if versioningBytes, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists {
|
|
return string(versioningBytes) // "Enabled" or "Suspended"
|
|
}
|
|
|
|
return "" // Never enabled
|
|
}
|
|
|
|
// CreateObjectLockConfiguration creates a new ObjectLockConfiguration with the specified parameters
|
|
func CreateObjectLockConfiguration(enabled bool, mode string, days int, years int) *ObjectLockConfiguration {
|
|
if !enabled {
|
|
return nil
|
|
}
|
|
|
|
config := &ObjectLockConfiguration{
|
|
ObjectLockEnabled: s3_constants.ObjectLockEnabled,
|
|
}
|
|
|
|
// Add default retention rule if mode and period are specified
|
|
if mode != "" && (days > 0 || years > 0) {
|
|
config.Rule = &ObjectLockRule{
|
|
DefaultRetention: &DefaultRetention{
|
|
Mode: mode,
|
|
Days: days,
|
|
Years: years,
|
|
DaysSet: days > 0,
|
|
YearsSet: years > 0,
|
|
},
|
|
}
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
// StoreObjectLockConfigurationInExtended stores Object Lock configuration in entry extended attributes
|
|
func StoreObjectLockConfigurationInExtended(entry *filer_pb.Entry, config *ObjectLockConfiguration) error {
|
|
if entry.Extended == nil {
|
|
entry.Extended = make(map[string][]byte)
|
|
}
|
|
|
|
if config == nil {
|
|
// Remove Object Lock configuration
|
|
delete(entry.Extended, s3_constants.ExtObjectLockEnabledKey)
|
|
delete(entry.Extended, s3_constants.ExtObjectLockDefaultModeKey)
|
|
delete(entry.Extended, s3_constants.ExtObjectLockDefaultDaysKey)
|
|
delete(entry.Extended, s3_constants.ExtObjectLockDefaultYearsKey)
|
|
return nil
|
|
}
|
|
|
|
// Store the enabled flag
|
|
entry.Extended[s3_constants.ExtObjectLockEnabledKey] = []byte(config.ObjectLockEnabled)
|
|
|
|
// Store default retention configuration if present
|
|
if config.Rule != nil && config.Rule.DefaultRetention != nil {
|
|
defaultRetention := config.Rule.DefaultRetention
|
|
|
|
// Store mode
|
|
if defaultRetention.Mode != "" {
|
|
entry.Extended[s3_constants.ExtObjectLockDefaultModeKey] = []byte(defaultRetention.Mode)
|
|
}
|
|
|
|
// Store days
|
|
if defaultRetention.DaysSet && defaultRetention.Days > 0 {
|
|
entry.Extended[s3_constants.ExtObjectLockDefaultDaysKey] = []byte(strconv.Itoa(defaultRetention.Days))
|
|
}
|
|
|
|
// Store years
|
|
if defaultRetention.YearsSet && defaultRetention.Years > 0 {
|
|
entry.Extended[s3_constants.ExtObjectLockDefaultYearsKey] = []byte(strconv.Itoa(defaultRetention.Years))
|
|
}
|
|
} else {
|
|
// Remove default retention if not present
|
|
delete(entry.Extended, s3_constants.ExtObjectLockDefaultModeKey)
|
|
delete(entry.Extended, s3_constants.ExtObjectLockDefaultDaysKey)
|
|
delete(entry.Extended, s3_constants.ExtObjectLockDefaultYearsKey)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadObjectLockConfigurationFromExtended loads Object Lock configuration from entry extended attributes
|
|
func LoadObjectLockConfigurationFromExtended(entry *filer_pb.Entry) (*ObjectLockConfiguration, bool) {
|
|
if entry == nil || entry.Extended == nil {
|
|
return nil, false
|
|
}
|
|
|
|
// Check if Object Lock is enabled
|
|
enabledBytes, exists := entry.Extended[s3_constants.ExtObjectLockEnabledKey]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
enabled := string(enabledBytes)
|
|
if enabled != s3_constants.ObjectLockEnabled && enabled != "true" {
|
|
return nil, false
|
|
}
|
|
|
|
// Create basic configuration
|
|
config := &ObjectLockConfiguration{
|
|
ObjectLockEnabled: s3_constants.ObjectLockEnabled,
|
|
}
|
|
|
|
// Load default retention configuration if present
|
|
if modeBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultModeKey]; exists {
|
|
mode := string(modeBytes)
|
|
|
|
// Parse days and years
|
|
var days, years int
|
|
if daysBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultDaysKey]; exists {
|
|
if parsed, err := strconv.Atoi(string(daysBytes)); err == nil {
|
|
days = parsed
|
|
}
|
|
}
|
|
if yearsBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultYearsKey]; exists {
|
|
if parsed, err := strconv.Atoi(string(yearsBytes)); err == nil {
|
|
years = parsed
|
|
}
|
|
}
|
|
|
|
// Create rule if we have a mode and at least days or years
|
|
if mode != "" && (days > 0 || years > 0) {
|
|
config.Rule = &ObjectLockRule{
|
|
DefaultRetention: &DefaultRetention{
|
|
Mode: mode,
|
|
Days: days,
|
|
Years: years,
|
|
DaysSet: days > 0,
|
|
YearsSet: years > 0,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
return config, true
|
|
}
|
|
|
|
// ExtractObjectLockInfoFromConfig extracts basic Object Lock information from configuration
|
|
// Returns: enabled, mode, duration (for UI display)
|
|
func ExtractObjectLockInfoFromConfig(config *ObjectLockConfiguration) (bool, string, int32) {
|
|
if config == nil || config.ObjectLockEnabled != s3_constants.ObjectLockEnabled {
|
|
return false, "", 0
|
|
}
|
|
|
|
if config.Rule == nil || config.Rule.DefaultRetention == nil {
|
|
return true, "", 0
|
|
}
|
|
|
|
defaultRetention := config.Rule.DefaultRetention
|
|
|
|
// Convert years to days for consistent representation
|
|
days := 0
|
|
if defaultRetention.DaysSet {
|
|
days = defaultRetention.Days
|
|
}
|
|
if defaultRetention.YearsSet && defaultRetention.Years > 0 {
|
|
days += defaultRetention.Years * 365
|
|
}
|
|
|
|
return true, defaultRetention.Mode, int32(days)
|
|
}
|
|
|
|
// CreateObjectLockConfigurationFromParams creates ObjectLockConfiguration from individual parameters
|
|
// This is a convenience function for Admin UI usage
|
|
func CreateObjectLockConfigurationFromParams(enabled bool, mode string, duration int32) *ObjectLockConfiguration {
|
|
if !enabled {
|
|
return nil
|
|
}
|
|
|
|
return CreateObjectLockConfiguration(enabled, mode, int(duration), 0)
|
|
}
|
|
|
|
// ValidateObjectLockParameters validates Object Lock parameters before creating configuration
|
|
func ValidateObjectLockParameters(enabled bool, mode string, duration int32) error {
|
|
if !enabled {
|
|
return nil
|
|
}
|
|
|
|
if mode != s3_constants.RetentionModeGovernance && mode != s3_constants.RetentionModeCompliance {
|
|
return ErrInvalidObjectLockMode
|
|
}
|
|
|
|
if duration <= 0 {
|
|
return ErrInvalidObjectLockDuration
|
|
}
|
|
|
|
if duration > MaxRetentionDays {
|
|
return ErrObjectLockDurationExceeded
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ====================================================================
|
|
// OBJECT LOCK VALIDATION FUNCTIONS
|
|
// ====================================================================
|
|
// These validation functions provide comprehensive validation for
|
|
// all Object Lock related configurations and requests.
|
|
|
|
// ValidateRetention validates retention configuration for object-level retention
|
|
func ValidateRetention(retention *ObjectRetention) error {
|
|
// Check if mode is specified
|
|
if retention.Mode == "" {
|
|
return ErrRetentionMissingMode
|
|
}
|
|
|
|
// Check if retain until date is specified
|
|
if retention.RetainUntilDate == nil {
|
|
return ErrRetentionMissingRetainUntilDate
|
|
}
|
|
|
|
// Check if mode is valid
|
|
if retention.Mode != s3_constants.RetentionModeGovernance && retention.Mode != s3_constants.RetentionModeCompliance {
|
|
return ErrInvalidRetentionModeValue
|
|
}
|
|
|
|
// Check if retain until date is in the future
|
|
if retention.RetainUntilDate.Before(time.Now()) {
|
|
return ErrRetentionDateMustBeFuture
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateLegalHold validates legal hold configuration
|
|
func ValidateLegalHold(legalHold *ObjectLegalHold) error {
|
|
// Check if status is valid
|
|
if legalHold.Status != s3_constants.LegalHoldOn && legalHold.Status != s3_constants.LegalHoldOff {
|
|
return ErrInvalidLegalHoldStatus
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateObjectLockConfiguration validates object lock configuration at bucket level
|
|
func ValidateObjectLockConfiguration(config *ObjectLockConfiguration) error {
|
|
// ObjectLockEnabled is required for bucket-level configuration
|
|
if config.ObjectLockEnabled == "" {
|
|
return ErrObjectLockConfigurationMissingEnabled
|
|
}
|
|
|
|
// Validate ObjectLockEnabled value
|
|
if config.ObjectLockEnabled != s3_constants.ObjectLockEnabled {
|
|
// ObjectLockEnabled can only be 'Enabled', any other value (including 'Disabled') is malformed XML
|
|
return ErrInvalidObjectLockEnabledValue
|
|
}
|
|
|
|
// Validate Rule if present
|
|
if config.Rule != nil {
|
|
if config.Rule.DefaultRetention == nil {
|
|
return ErrRuleMissingDefaultRetention
|
|
}
|
|
return validateDefaultRetention(config.Rule.DefaultRetention)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateDefaultRetention validates default retention configuration for bucket-level settings
|
|
func validateDefaultRetention(retention *DefaultRetention) error {
|
|
glog.V(2).Infof("validateDefaultRetention: Mode=%s, Days=%d (set=%v), Years=%d (set=%v)",
|
|
retention.Mode, retention.Days, retention.DaysSet, retention.Years, retention.YearsSet)
|
|
|
|
// Mode is required
|
|
if retention.Mode == "" {
|
|
return ErrDefaultRetentionMissingMode
|
|
}
|
|
|
|
// Mode must be valid
|
|
if retention.Mode != s3_constants.RetentionModeGovernance && retention.Mode != s3_constants.RetentionModeCompliance {
|
|
return ErrInvalidDefaultRetentionMode
|
|
}
|
|
|
|
// Check for invalid Years value (negative values are always invalid)
|
|
if retention.YearsSet && retention.Years < 0 {
|
|
return ErrInvalidRetentionPeriod
|
|
}
|
|
|
|
// Check for invalid Days value (negative values are invalid)
|
|
if retention.DaysSet && retention.Days < 0 {
|
|
return ErrInvalidRetentionPeriod
|
|
}
|
|
|
|
// Check for invalid Days value (zero is invalid when explicitly provided)
|
|
if retention.DaysSet && retention.Days == 0 {
|
|
return ErrInvalidRetentionPeriod
|
|
}
|
|
|
|
// Check for neither Days nor Years being specified
|
|
if !retention.DaysSet && !retention.YearsSet {
|
|
return ErrDefaultRetentionMissingPeriod
|
|
}
|
|
|
|
// Check for both Days and Years being specified
|
|
if retention.DaysSet && retention.YearsSet {
|
|
return ErrDefaultRetentionBothDaysAndYears
|
|
}
|
|
|
|
// Validate Days if specified
|
|
if retention.DaysSet && retention.Days > 0 {
|
|
if retention.Days > MaxRetentionDays {
|
|
return ErrDefaultRetentionDaysOutOfRange
|
|
}
|
|
}
|
|
|
|
// Validate Years if specified
|
|
if retention.YearsSet && retention.Years > 0 {
|
|
if retention.Years > MaxRetentionYears {
|
|
return ErrDefaultRetentionYearsOutOfRange
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasObjectsWithActiveLocks checks if any objects in the bucket have active retention or legal hold
|
|
// Delegates to s3_objectlock.HasObjectsWithActiveLocks
|
|
func HasObjectsWithActiveLocks(ctx context.Context, client filer_pb.SeaweedFilerClient, bucketPath string) (bool, error) {
|
|
return s3_objectlock.HasObjectsWithActiveLocks(ctx, client, bucketPath)
|
|
}
|
|
|
|
// CheckBucketForLockedObjects is a unified function that checks if a bucket has Object Lock enabled
|
|
// and if so, scans for objects with active locks.
|
|
// Delegates to s3_objectlock.CheckBucketForLockedObjects
|
|
func CheckBucketForLockedObjects(ctx context.Context, client filer_pb.SeaweedFilerClient, bucketsPath, bucketName string) error {
|
|
return s3_objectlock.CheckBucketForLockedObjects(ctx, client, bucketsPath, bucketName)
|
|
}
|