mirror of
https://github.com/versity/versitygw.git
synced 2026-01-09 13:03:09 +00:00
Added support to enable object lock on bucket creation in posix and azure backends. Implemented the logic to add object legal hold and retention on object creation in azure and posix backends. Added the functionality for HeadObject to return object lock related headers. Added integration tests for these features.
231 lines
6.4 KiB
Go
231 lines
6.4 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
|
"github.com/versity/versitygw/backend"
|
|
"github.com/versity/versitygw/s3err"
|
|
)
|
|
|
|
type BucketLockConfig struct {
|
|
Enabled bool
|
|
DefaultRetention *types.DefaultRetention
|
|
CreatedAt *time.Time
|
|
}
|
|
|
|
func ParseBucketLockConfigurationInput(input []byte) ([]byte, error) {
|
|
var lockConfig types.ObjectLockConfiguration
|
|
if err := xml.Unmarshal(input, &lockConfig); err != nil {
|
|
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
|
}
|
|
|
|
config := BucketLockConfig{
|
|
Enabled: lockConfig.ObjectLockEnabled == types.ObjectLockEnabledEnabled,
|
|
}
|
|
|
|
if lockConfig.Rule != nil && lockConfig.Rule.DefaultRetention != nil {
|
|
retention := lockConfig.Rule.DefaultRetention
|
|
if retention.Years != nil && retention.Days != nil {
|
|
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
|
}
|
|
|
|
config.DefaultRetention = retention
|
|
now := time.Now()
|
|
config.CreatedAt = &now
|
|
}
|
|
|
|
return json.Marshal(config)
|
|
}
|
|
|
|
func ParseBucketLockConfigurationOutput(input []byte) (*types.ObjectLockConfiguration, error) {
|
|
var config BucketLockConfig
|
|
if err := json.Unmarshal(input, &config); err != nil {
|
|
return nil, fmt.Errorf("parse object lock config: %w", err)
|
|
}
|
|
|
|
result := &types.ObjectLockConfiguration{
|
|
Rule: &types.ObjectLockRule{
|
|
DefaultRetention: config.DefaultRetention,
|
|
},
|
|
}
|
|
|
|
if config.Enabled {
|
|
result.ObjectLockEnabled = types.ObjectLockEnabledEnabled
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func ParseObjectLockRetentionInput(input []byte) ([]byte, error) {
|
|
var retention types.ObjectLockRetention
|
|
if err := xml.Unmarshal(input, &retention); err != nil {
|
|
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
|
}
|
|
|
|
if retention.RetainUntilDate == nil || retention.RetainUntilDate.Before(time.Now()) {
|
|
return nil, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate)
|
|
}
|
|
switch retention.Mode {
|
|
case types.ObjectLockRetentionModeCompliance:
|
|
case types.ObjectLockRetentionModeGovernance:
|
|
default:
|
|
return nil, s3err.GetAPIError(s3err.ErrInvalidRequest)
|
|
}
|
|
|
|
return json.Marshal(retention)
|
|
}
|
|
|
|
func ParseObjectLockRetentionOutput(input []byte) (*types.ObjectLockRetention, error) {
|
|
var retention types.ObjectLockRetention
|
|
if err := json.Unmarshal(input, &retention); err != nil {
|
|
return nil, fmt.Errorf("parse object lock retention: %w", err)
|
|
}
|
|
|
|
return &retention, nil
|
|
}
|
|
|
|
func ParseObjectLegalHoldOutput(status *bool) *types.ObjectLockLegalHold {
|
|
if status == nil {
|
|
return nil
|
|
}
|
|
|
|
if *status {
|
|
return &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOn,
|
|
}
|
|
}
|
|
|
|
return &types.ObjectLockLegalHold{
|
|
Status: types.ObjectLockLegalHoldStatusOff,
|
|
}
|
|
}
|
|
|
|
func CheckObjectAccess(ctx context.Context, bucket, userAccess string, objects []string, isAdminOrRoot bool, be backend.Backend) error {
|
|
data, err := be.GetObjectLockConfiguration(ctx, bucket)
|
|
if err != nil {
|
|
if errors.Is(err, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)) {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
var bucketLockConfig BucketLockConfig
|
|
if err := json.Unmarshal(data, &bucketLockConfig); err != nil {
|
|
return fmt.Errorf("parse object lock config: %w", err)
|
|
}
|
|
|
|
if !bucketLockConfig.Enabled {
|
|
return nil
|
|
}
|
|
|
|
objExists := true
|
|
|
|
for _, obj := range objects {
|
|
var checkRetention bool = true
|
|
retentionData, err := be.GetObjectRetention(ctx, bucket, obj, "")
|
|
if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) {
|
|
objExists = false
|
|
continue
|
|
}
|
|
if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) {
|
|
checkRetention = false
|
|
}
|
|
if err != nil && checkRetention {
|
|
return err
|
|
}
|
|
|
|
if checkRetention {
|
|
retention, err := ParseObjectLockRetentionOutput(retentionData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if retention.Mode != "" && retention.RetainUntilDate != nil {
|
|
if retention.RetainUntilDate.After(time.Now()) {
|
|
switch retention.Mode {
|
|
case types.ObjectLockRetentionModeGovernance:
|
|
if !isAdminOrRoot {
|
|
policy, err := be.GetBucketPolicy(ctx, bucket)
|
|
if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchBucketPolicy)) {
|
|
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = verifyBucketPolicy(policy, userAccess, bucket, obj, BypassGovernanceRetentionAction)
|
|
if err != nil {
|
|
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
|
}
|
|
}
|
|
case types.ObjectLockRetentionModeCompliance:
|
|
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
status, err := be.GetObjectLegalHold(ctx, bucket, obj, "")
|
|
if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if *status && !isAdminOrRoot {
|
|
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
|
}
|
|
}
|
|
|
|
if bucketLockConfig.DefaultRetention != nil && bucketLockConfig.CreatedAt != nil && objExists {
|
|
expirationDate := *bucketLockConfig.CreatedAt
|
|
if bucketLockConfig.DefaultRetention.Days != nil {
|
|
expirationDate = expirationDate.AddDate(0, 0, int(*bucketLockConfig.DefaultRetention.Days))
|
|
}
|
|
if bucketLockConfig.DefaultRetention.Years != nil {
|
|
expirationDate = expirationDate.AddDate(int(*bucketLockConfig.DefaultRetention.Years), 0, 0)
|
|
}
|
|
|
|
if expirationDate.After(time.Now()) {
|
|
switch bucketLockConfig.DefaultRetention.Mode {
|
|
case types.ObjectLockRetentionModeGovernance:
|
|
if !isAdminOrRoot {
|
|
policy, err := be.GetBucketPolicy(ctx, bucket)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = verifyBucketPolicy(policy, userAccess, bucket, "", BypassGovernanceRetentionAction)
|
|
if err != nil {
|
|
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
|
}
|
|
}
|
|
case types.ObjectLockRetentionModeCompliance:
|
|
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|