Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
473 lines
16 KiB
Go
473 lines
16 KiB
Go
package s3api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/iam/integration"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
)
|
|
|
|
// Bucket policy metadata key for storing policies in filer.
|
|
// Also consumed directly by weed/admin/dash for the admin UI's bucket
|
|
// policy management, so keep it exported and don't change its value
|
|
// without updating that package too.
|
|
const BUCKET_POLICY_METADATA_KEY = "s3-bucket-policy"
|
|
|
|
// Sentinel errors for bucket policy operations
|
|
var (
|
|
ErrPolicyNotFound = errors.New("bucket policy not found")
|
|
// ErrBucketNotFound is already defined in s3api_object_retention.go
|
|
)
|
|
|
|
// GetBucketPolicyHandler handles GET bucket?policy requests
|
|
func (s3a *S3ApiServer) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("GetBucketPolicyHandler: bucket=%s", bucket)
|
|
|
|
// Validate bucket exists first for correct error mapping
|
|
_, err := s3a.getBucketEntry(bucket)
|
|
if err != nil {
|
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
|
} else {
|
|
glog.Errorf("Failed to check bucket existence for %s: %v", bucket, err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Get bucket policy from filer metadata
|
|
policyDocument, err := s3a.getBucketPolicy(bucket)
|
|
if err != nil {
|
|
if errors.Is(err, ErrPolicyNotFound) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketPolicy)
|
|
} else if errors.Is(err, ErrBucketNotFound) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
|
} else {
|
|
glog.Errorf("Failed to get bucket policy for %s: %v", bucket, err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Return policy as JSON
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
if err := json.NewEncoder(w).Encode(policyDocument); err != nil {
|
|
glog.Errorf("Failed to encode bucket policy response: %v", err)
|
|
}
|
|
}
|
|
|
|
// PutBucketPolicyHandler handles PUT bucket?policy requests
|
|
func (s3a *S3ApiServer) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("PutBucketPolicyHandler: bucket=%s", bucket)
|
|
|
|
// Read policy document from request body
|
|
body, err := io.ReadAll(io.LimitReader(r.Body, policy_engine.MaxBucketPolicySize+1))
|
|
if err != nil {
|
|
glog.Errorf("Failed to read bucket policy request body: %v", err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument)
|
|
return
|
|
}
|
|
defer r.Body.Close()
|
|
|
|
if len(body) > policy_engine.MaxBucketPolicySize {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrPolicyTooLarge)
|
|
return
|
|
}
|
|
|
|
// Parse and validate policy document
|
|
var policyDoc policy_engine.PolicyDocument
|
|
if err := json.Unmarshal(body, &policyDoc); err != nil {
|
|
glog.Errorf("Failed to parse bucket policy JSON: %v", err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrMalformedPolicy)
|
|
return
|
|
}
|
|
|
|
// Validate core policy structure (Effect, Action, etc.)
|
|
if err := policy_engine.ValidatePolicy(&policyDoc); err != nil {
|
|
glog.Errorf("Policy validation failed: %v", err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument)
|
|
return
|
|
}
|
|
|
|
// Additional bucket policy specific validation
|
|
if err := policy_engine.ValidateBucketPolicy(&policyDoc, bucket); err != nil {
|
|
glog.Errorf("Bucket policy validation failed: %v", err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument)
|
|
return
|
|
}
|
|
|
|
// Store bucket policy
|
|
if err := s3a.setBucketPolicy(bucket, &policyDoc); err != nil {
|
|
glog.Errorf("Failed to store bucket policy for %s: %v", bucket, err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
return
|
|
}
|
|
|
|
// Immediately load into policy engine to avoid race condition
|
|
// (The subscription system will also do this async, but we want immediate effect)
|
|
if s3a.policyEngine != nil {
|
|
if err := s3a.policyEngine.LoadBucketPolicyFromCache(bucket, &policyDoc); err != nil {
|
|
glog.Warningf("Failed to immediately load bucket policy into engine for %s: %v", bucket, err)
|
|
// Don't fail the request since the subscription will eventually sync it
|
|
}
|
|
}
|
|
|
|
// Update IAM integration with new bucket policy
|
|
if s3a.iam.iamIntegration != nil {
|
|
if err := s3a.updateBucketPolicyInIAM(bucket, &policyDoc); err != nil {
|
|
glog.Errorf("Failed to update IAM with bucket policy: %v", err)
|
|
// Don't fail the request, but log the warning
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// DeleteBucketPolicyHandler handles DELETE bucket?policy requests
|
|
func (s3a *S3ApiServer) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
|
bucket, _ := s3_constants.GetBucketAndObject(r)
|
|
|
|
glog.V(3).Infof("DeleteBucketPolicyHandler: bucket=%s", bucket)
|
|
|
|
// Validate bucket exists first for correct error mapping
|
|
_, err := s3a.getBucketEntry(bucket)
|
|
if err != nil {
|
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
|
} else {
|
|
glog.Errorf("Failed to check bucket existence for %s: %v", bucket, err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Check if bucket policy exists
|
|
if _, err := s3a.getBucketPolicy(bucket); err != nil {
|
|
if errors.Is(err, ErrPolicyNotFound) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketPolicy)
|
|
} else if errors.Is(err, ErrBucketNotFound) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
|
|
} else {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Delete bucket policy
|
|
if err := s3a.deleteBucketPolicy(bucket); err != nil {
|
|
glog.Errorf("Failed to delete bucket policy for %s: %v", bucket, err)
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
|
|
return
|
|
}
|
|
|
|
// Immediately remove from policy engine to avoid race condition
|
|
// (The subscription system will also do this async, but we want immediate effect)
|
|
if s3a.policyEngine != nil {
|
|
if err := s3a.policyEngine.DeleteBucketPolicy(bucket); err != nil {
|
|
glog.Warningf("Failed to immediately remove bucket policy from engine for %s: %v", bucket, err)
|
|
// Don't fail the request since the subscription will eventually sync it
|
|
}
|
|
}
|
|
|
|
// Update IAM integration to remove bucket policy
|
|
if s3a.iam.iamIntegration != nil {
|
|
if err := s3a.removeBucketPolicyFromIAM(bucket); err != nil {
|
|
glog.Errorf("Failed to remove bucket policy from IAM: %v", err)
|
|
// Don't fail the request, but log the warning
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// Helper functions for bucket policy storage and retrieval
|
|
|
|
// getBucketPolicy retrieves a bucket policy from filer metadata
|
|
// getBucketPolicy retrieves the bucket policy from filer
|
|
func (s3a *S3ApiServer) getBucketPolicy(bucket string) (*policy_engine.PolicyDocument, error) {
|
|
|
|
var policyDoc policy_engine.PolicyDocument
|
|
err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
|
|
Directory: s3a.bucketRoot(bucket),
|
|
Name: bucket,
|
|
})
|
|
if err != nil {
|
|
// Return sentinel error for bucket not found
|
|
return fmt.Errorf("%w: %v", ErrBucketNotFound, err)
|
|
}
|
|
|
|
if resp.Entry == nil {
|
|
return ErrPolicyNotFound
|
|
}
|
|
|
|
policyJSON, exists := resp.Entry.Extended[BUCKET_POLICY_METADATA_KEY]
|
|
if !exists || len(policyJSON) == 0 {
|
|
return ErrPolicyNotFound
|
|
}
|
|
|
|
if err := json.Unmarshal(policyJSON, &policyDoc); err != nil {
|
|
return fmt.Errorf("failed to parse stored bucket policy: %v", err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &policyDoc, nil
|
|
}
|
|
|
|
// setBucketPolicy stores a bucket policy in filer metadata
|
|
func (s3a *S3ApiServer) setBucketPolicy(bucket string, policyDoc *policy_engine.PolicyDocument) error {
|
|
// Serialize policy to JSON
|
|
policyJSON, err := json.Marshal(policyDoc)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to serialize policy: %v", err)
|
|
}
|
|
|
|
return s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
// First, get the current entry to preserve other attributes
|
|
resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
|
|
Directory: s3a.bucketRoot(bucket),
|
|
Name: bucket,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("bucket not found: %v", err)
|
|
}
|
|
|
|
entry := resp.Entry
|
|
if entry.Extended == nil {
|
|
entry.Extended = make(map[string][]byte)
|
|
}
|
|
|
|
// Set the bucket policy metadata
|
|
entry.Extended[BUCKET_POLICY_METADATA_KEY] = policyJSON
|
|
|
|
// Update the entry with new metadata
|
|
_, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
|
|
Directory: s3a.bucketRoot(bucket),
|
|
Entry: entry,
|
|
})
|
|
|
|
return err
|
|
})
|
|
}
|
|
|
|
// deleteBucketPolicy removes a bucket policy from filer metadata
|
|
func (s3a *S3ApiServer) deleteBucketPolicy(bucket string) error {
|
|
return s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
|
|
// Get the current entry
|
|
resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
|
|
Directory: s3a.bucketRoot(bucket),
|
|
Name: bucket,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("bucket not found: %v", err)
|
|
}
|
|
|
|
entry := resp.Entry
|
|
if entry.Extended == nil {
|
|
return nil // No policy to delete
|
|
}
|
|
|
|
// Remove the bucket policy metadata
|
|
delete(entry.Extended, BUCKET_POLICY_METADATA_KEY)
|
|
|
|
// Update the entry
|
|
_, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
|
|
Directory: s3a.bucketRoot(bucket),
|
|
Entry: entry,
|
|
})
|
|
|
|
return err
|
|
})
|
|
}
|
|
|
|
// IAM integration functions
|
|
|
|
// updateBucketPolicyInIAM updates the IAM system with the new bucket policy
|
|
func (s3a *S3ApiServer) updateBucketPolicyInIAM(bucket string, policyDoc *policy_engine.PolicyDocument) error {
|
|
iamManager := s3a.bucketPolicyIAMManager()
|
|
if iamManager == nil {
|
|
return nil
|
|
}
|
|
|
|
policyJSON, err := json.Marshal(policyDoc)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal policy: %w", err)
|
|
}
|
|
|
|
glog.V(2).Infof("Updated bucket policy for %s in IAM system", bucket)
|
|
return iamManager.UpdateBucketPolicy(context.Background(), bucket, policyJSON)
|
|
}
|
|
|
|
// ensureBucketPolicyInIAM backfills the IAM mirror for a policy the
|
|
// subscription never saw change (one that predates the IAM integration).
|
|
// Called from the lazy bucket-config load; a present mirror is left alone.
|
|
func (s3a *S3ApiServer) ensureBucketPolicyInIAM(bucket string, policyJSON []byte) {
|
|
iamManager := s3a.bucketPolicyIAMManager()
|
|
if iamManager == nil {
|
|
return
|
|
}
|
|
|
|
wrote, err := iamManager.EnsureBucketPolicy(context.Background(), bucket, policyJSON)
|
|
if err != nil {
|
|
glog.Warningf("backfill bucket policy for %s into IAM: %v", bucket, err)
|
|
return
|
|
}
|
|
if !wrote {
|
|
return
|
|
}
|
|
|
|
// The check-then-write above can race a concurrent policy change or
|
|
// delete: the event-driven mirror may have landed in between, and this
|
|
// write would then have re-stored bytes that are already stale - with
|
|
// no later event to heal it. Reconcile against a fresh entry read,
|
|
// which is authoritative; anything changing after this read fires its
|
|
// own event, and the mirror for it finds this write already present.
|
|
entry, err := s3a.getBucketEntry(bucket)
|
|
if err != nil {
|
|
if errors.Is(err, filer_pb.ErrNotFound) {
|
|
if err := s3a.removeBucketPolicyFromIAM(bucket); err != nil {
|
|
glog.Warningf("remove bucket policy for %s from IAM: %v", bucket, err)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
current := entry.Extended[BUCKET_POLICY_METADATA_KEY]
|
|
if bytes.Equal(current, policyJSON) {
|
|
return
|
|
}
|
|
if len(current) == 0 {
|
|
if err := s3a.removeBucketPolicyFromIAM(bucket); err != nil {
|
|
glog.Warningf("remove bucket policy for %s from IAM: %v", bucket, err)
|
|
}
|
|
return
|
|
}
|
|
var policyDoc policy_engine.PolicyDocument
|
|
if err := json.Unmarshal(current, &policyDoc); err != nil {
|
|
glog.Warningf("backfill bucket policy for %s into IAM: parse: %v", bucket, err)
|
|
return
|
|
}
|
|
if err := s3a.updateBucketPolicyInIAM(bucket, &policyDoc); err != nil {
|
|
glog.Warningf("backfill bucket policy for %s into IAM: %v", bucket, err)
|
|
}
|
|
}
|
|
|
|
// removeBucketPolicyFromIAM removes the bucket policy from the IAM system
|
|
func (s3a *S3ApiServer) removeBucketPolicyFromIAM(bucket string) error {
|
|
iamManager := s3a.bucketPolicyIAMManager()
|
|
if iamManager == nil {
|
|
return nil
|
|
}
|
|
|
|
glog.V(2).Infof("Removed bucket policy for %s from IAM system", bucket)
|
|
return iamManager.RemoveBucketPolicy(context.Background(), bucket)
|
|
}
|
|
|
|
// bucketPolicyIAMManager returns the advanced-IAM manager the
|
|
// "bucket-policy:<bucket>" mirror lives in, or nil when the integration is
|
|
// not enabled.
|
|
func (s3a *S3ApiServer) bucketPolicyIAMManager() *integration.IAMManager {
|
|
if s3a.iam == nil || s3a.iam.iamIntegration == nil {
|
|
return nil
|
|
}
|
|
if s3Integration, ok := s3a.iam.iamIntegration.(*S3IAMIntegration); ok {
|
|
return s3Integration.iamManager
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// mirrorBucketPolicyToIAM keeps the "bucket-policy:<bucket>" IAM mirror in
|
|
// sync with the policy stored on a bucket's filer entry. Driven from the
|
|
// metadata subscription so it covers every writer - this gateway's own
|
|
// PutBucketPolicy, another gateway's, the admin UI, bucket deletion, and
|
|
// rename - where the handlers' direct calls only ever covered the first.
|
|
func (s3a *S3ApiServer) mirrorBucketPolicyToIAM(oldEntry, newEntry *filer_pb.Entry) {
|
|
if s3a.bucketPolicyIAMManager() == nil {
|
|
return
|
|
}
|
|
removeName, updateName, updatePolicy := bucketPolicyMirrorOps(oldEntry, newEntry)
|
|
if removeName != "" {
|
|
if err := s3a.removeBucketPolicyFromIAM(removeName); err != nil {
|
|
glog.Warningf("remove bucket policy for %s from IAM: %v", removeName, err)
|
|
}
|
|
}
|
|
if updateName == "" {
|
|
return
|
|
}
|
|
var policyDoc policy_engine.PolicyDocument
|
|
if err := json.Unmarshal(updatePolicy, &policyDoc); err != nil {
|
|
glog.Warningf("mirror bucket policy for %s to IAM: parse: %v", updateName, err)
|
|
return
|
|
}
|
|
if err := s3a.updateBucketPolicyInIAM(updateName, &policyDoc); err != nil {
|
|
glog.Warningf("mirror bucket policy for %s to IAM: %v", updateName, err)
|
|
}
|
|
}
|
|
|
|
// bucketPolicyMirrorOps computes what a bucket entry change means for the
|
|
// IAM mirror: a name whose mirror must be removed, and a (name, policy) to
|
|
// write. A rename delivers both entries under different names in one event,
|
|
// and the old name's mirror has to move even when the policy bytes are
|
|
// unchanged - equality only short-circuits same-name updates.
|
|
func bucketPolicyMirrorOps(oldEntry, newEntry *filer_pb.Entry) (removeName, updateName string, updatePolicy []byte) {
|
|
var oldName, newName string
|
|
var oldPolicy, newPolicy []byte
|
|
if oldEntry != nil {
|
|
oldName = oldEntry.Name
|
|
oldPolicy = oldEntry.Extended[BUCKET_POLICY_METADATA_KEY]
|
|
}
|
|
if newEntry != nil {
|
|
newName = newEntry.Name
|
|
newPolicy = newEntry.Extended[BUCKET_POLICY_METADATA_KEY]
|
|
}
|
|
if oldName != "" && oldName != newName && len(oldPolicy) > 0 {
|
|
removeName = oldName
|
|
oldPolicy = nil
|
|
}
|
|
if newName == "" || bytes.Equal(oldPolicy, newPolicy) {
|
|
return
|
|
}
|
|
if len(newPolicy) == 0 {
|
|
removeName = newName
|
|
return
|
|
}
|
|
updateName = newName
|
|
updatePolicy = newPolicy
|
|
return
|
|
}
|
|
|
|
// GetPublicAccessBlockHandler Retrieves the PublicAccessBlock configuration for an S3 bucket
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetPublicAccessBlock.html
|
|
func (s3a *S3ApiServer) GetPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
|
|
}
|
|
|
|
func (s3a *S3ApiServer) PutPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
|
|
}
|
|
|
|
func (s3a *S3ApiServer) DeletePublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
|
|
s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
|
|
}
|