snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
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
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
This commit is contained in:
260
weed/s3api/s3err/audit_fluent.go
Normal file
260
weed/s3api/s3err/audit_fluent.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fluent/fluent-logger-golang/fluent"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/request_id"
|
||||
)
|
||||
|
||||
type AccessLogExtend struct {
|
||||
AccessLog
|
||||
AccessLogHTTP
|
||||
}
|
||||
|
||||
type AccessLog struct {
|
||||
Bucket string `msg:"bucket" json:"bucket"` // awsexamplebucket1
|
||||
Time int64 `msg:"time" json:"time"` // [06/Feb/2019:00:00:38 +0000]
|
||||
RemoteIP string `msg:"remote_ip" json:"remote_ip,omitempty"` // 192.0.2.3
|
||||
Requester string `msg:"requester" json:"requester,omitempty"` // IAM user id
|
||||
RequesterArn string `msg:"requester_arn" json:"requester_arn,omitempty"` // arn:aws:sts::0:assumed-role/Role/session
|
||||
RequesterIdentity string `msg:"requester_identity" json:"requester_identity,omitempty"` // authoritative OIDC identity claim (preferred_username/email/sub)
|
||||
RequestID string `msg:"request_id" json:"request_id,omitempty"` // 3E57427F33A59F07
|
||||
Operation string `msg:"operation" json:"operation,omitempty"` // REST.HTTP_method.resource_type REST.PUT.OBJECT
|
||||
Key string `msg:"key" json:"key,omitempty"` // /photos/2019/08/puppy.jpg
|
||||
ErrorCode string `msg:"error_code" json:"error_code,omitempty"`
|
||||
HostId string `msg:"host_id" json:"host_id,omitempty"`
|
||||
HostHeader string `msg:"host_header" json:"host_header,omitempty"` // s3.us-west-2.amazonaws.com
|
||||
UserAgent string `msg:"user_agent" json:"user_agent,omitempty"`
|
||||
HTTPStatus int `msg:"status" json:"status,omitempty"`
|
||||
SignatureVersion string `msg:"signature_version" json:"signature_version,omitempty"`
|
||||
}
|
||||
|
||||
type AccessLogHTTP struct {
|
||||
RequestURI string `json:"request_uri,omitempty"` // "GET /awsexamplebucket1/photos/2019/08/puppy.jpg?x-foo=bar HTTP/1.1"
|
||||
BytesSent string `json:"bytes_sent,omitempty"`
|
||||
ObjectSize string `json:"object_size,omitempty"`
|
||||
TotalTime int `json:"total_time,omitempty"`
|
||||
TurnAroundTime int `json:"turn_around_time,omitempty"`
|
||||
Referer string `json:"Referer,omitempty"`
|
||||
VersionId string `json:"version_id,omitempty"`
|
||||
CipherSuite string `json:"cipher_suite,omitempty"`
|
||||
AuthenticationType string `json:"auth_type,omitempty"`
|
||||
TLSVersion string `json:"TLS_version,omitempty"`
|
||||
}
|
||||
|
||||
const tag = "s3.access"
|
||||
|
||||
var (
|
||||
Logger *fluent.Fluent
|
||||
hostname = os.Getenv("HOSTNAME")
|
||||
environment = os.Getenv("ENVIRONMENT")
|
||||
)
|
||||
|
||||
func InitAuditLog(config string) {
|
||||
configContent, readErr := os.ReadFile(config)
|
||||
if readErr != nil {
|
||||
glog.Errorf("fail to read fluent config %s : %v", config, readErr)
|
||||
return
|
||||
}
|
||||
fluentConfig := &fluent.Config{}
|
||||
if err := json.Unmarshal(configContent, fluentConfig); err != nil {
|
||||
glog.Errorf("fail to parse fluent config %s : %v", string(configContent), err)
|
||||
return
|
||||
}
|
||||
if len(fluentConfig.TagPrefix) == 0 && len(environment) > 0 {
|
||||
fluentConfig.TagPrefix = environment
|
||||
}
|
||||
fluentConfig.Async = true
|
||||
fluentConfig.AsyncResultCallback = func(data []byte, err error) {
|
||||
if err != nil {
|
||||
glog.Warning("Error while posting log: ", err)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
Logger, err = fluent.New(*fluentConfig)
|
||||
if err != nil {
|
||||
glog.Errorf("fail to load fluent config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getRemoteIP returns the client IP for the audit log, honoring forwarding
|
||||
// headers set by reverse proxies. Preference order: X-Forwarded-For (first
|
||||
// non-empty entry), X-Real-IP, then r.RemoteAddr. Headers are trusted as-is;
|
||||
// operators who expose the S3 endpoint directly to untrusted networks should
|
||||
// strip these headers at the proxy boundary so clients cannot spoof them.
|
||||
func getRemoteIP(r *http.Request) string {
|
||||
if forwardedFor := r.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
||||
for _, entry := range strings.Split(forwardedFor, ",") {
|
||||
if ip := strings.TrimSpace(entry); ip != "" {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
if realIP := strings.TrimSpace(r.Header.Get("X-Real-IP")); realIP != "" {
|
||||
return realIP
|
||||
}
|
||||
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
return host
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
func getREST(httpMetod string, resourceType string) string {
|
||||
return fmt.Sprintf("REST.%s.%s", httpMetod, resourceType)
|
||||
}
|
||||
|
||||
func getResourceType(object string, query_key string, method string) (string, bool) {
|
||||
if object == "/" {
|
||||
switch query_key {
|
||||
case "delete":
|
||||
return "BATCH.DELETE.OBJECT", true
|
||||
case "tagging":
|
||||
return getREST(method, "OBJECTTAGGING"), true
|
||||
case "lifecycle":
|
||||
return getREST(method, "LIFECYCLECONFIGURATION"), true
|
||||
case "acl":
|
||||
return getREST(method, "ACCESSCONTROLPOLICY"), true
|
||||
case "policy":
|
||||
return getREST(method, "BUCKETPOLICY"), true
|
||||
default:
|
||||
return getREST(method, "BUCKET"), false
|
||||
}
|
||||
} else {
|
||||
switch query_key {
|
||||
case "tagging":
|
||||
return getREST(method, "OBJECTTAGGING"), true
|
||||
default:
|
||||
return getREST(method, "OBJECT"), false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getOperation(object string, r *http.Request) string {
|
||||
queries := r.URL.Query()
|
||||
var operation string
|
||||
var queryFound bool
|
||||
for key, _ := range queries {
|
||||
operation, queryFound = getResourceType(object, key, r.Method)
|
||||
if queryFound {
|
||||
return operation
|
||||
}
|
||||
}
|
||||
if len(queries) == 0 {
|
||||
operation, _ = getResourceType(object, "", r.Method)
|
||||
}
|
||||
return operation
|
||||
}
|
||||
|
||||
func GetAccessLog(r *http.Request, HTTPStatusCode int, s3errCode ErrorCode) *AccessLog {
|
||||
bucket, key := s3_constants.GetBucketAndObject(r)
|
||||
var errorCode string
|
||||
if s3errCode != ErrNone {
|
||||
errorCode = GetAPIError(s3errCode).Code
|
||||
}
|
||||
remoteIP := getRemoteIP(r)
|
||||
hostHeader := r.Header.Get("X-Forwarded-Host")
|
||||
if len(hostHeader) == 0 {
|
||||
hostHeader = r.Host
|
||||
}
|
||||
return &AccessLog{
|
||||
HostHeader: hostHeader,
|
||||
RequestID: request_id.GetFromRequest(r),
|
||||
RemoteIP: remoteIP,
|
||||
Requester: s3_constants.GetIdentityNameFromContext(r), // Get from context, not header (secure)
|
||||
RequesterArn: s3_constants.GetPrincipalArnFromContext(r),
|
||||
RequesterIdentity: s3_constants.GetIdentityClaimFromContext(r),
|
||||
SignatureVersion: r.Header.Get(s3_constants.AmzAuthType),
|
||||
UserAgent: r.Header.Get("user-agent"),
|
||||
HostId: hostname,
|
||||
Bucket: bucket,
|
||||
HTTPStatus: HTTPStatusCode,
|
||||
Time: time.Now().Unix(),
|
||||
Key: key,
|
||||
Operation: getOperation(key, r),
|
||||
ErrorCode: errorCode,
|
||||
}
|
||||
}
|
||||
|
||||
func PostLog(r *http.Request, HTTPStatusCode int, errorCode ErrorCode) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
// Mark before the Logger nil-check so the middleware fallback in track()
|
||||
// still sees that audit was handled by the caller in deployments that
|
||||
// haven't configured fluent — keeps the flag behavior consistent and
|
||||
// makes wiring testable without standing up a fluent server.
|
||||
markAuditLogged(r)
|
||||
if Logger == nil {
|
||||
return
|
||||
}
|
||||
if err := Logger.Post(tag, *GetAccessLog(r, HTTPStatusCode, errorCode)); err != nil {
|
||||
glog.Warning("Error while posting log: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func PostAccessLog(log AccessLog) {
|
||||
if Logger == nil || len(log.Key) == 0 {
|
||||
return
|
||||
}
|
||||
if err := Logger.Post(tag, log); err != nil {
|
||||
glog.Warning("Error while posting log: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// auditLogCtxKey identifies the per-request flag used to detect whether an
|
||||
// audit entry has already been emitted, so middleware can supply a fallback
|
||||
// log for handlers that don't call PostLog themselves without double-logging
|
||||
// the ones that do.
|
||||
type auditLogCtxKey struct{}
|
||||
|
||||
// EnsureAuditTracking attaches an audit-tracking flag to the request context
|
||||
// if one is not already present. Safe to call when no fluent logger is
|
||||
// configured; the flag is harmless in that case.
|
||||
func EnsureAuditTracking(r *http.Request) *http.Request {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
if v, ok := r.Context().Value(auditLogCtxKey{}).(*atomic.Bool); ok && v != nil {
|
||||
return r
|
||||
}
|
||||
flag := new(atomic.Bool)
|
||||
return r.WithContext(context.WithValue(r.Context(), auditLogCtxKey{}, flag))
|
||||
}
|
||||
|
||||
// MarkAuditLogged signals that an audit entry has already been emitted for r.
|
||||
// Callers that emit logs via paths other than PostLog (e.g. PostAccessLog in
|
||||
// batch delete) should invoke this so the middleware fallback skips r.
|
||||
func MarkAuditLogged(r *http.Request) {
|
||||
markAuditLogged(r)
|
||||
}
|
||||
|
||||
func markAuditLogged(r *http.Request) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
if v, ok := r.Context().Value(auditLogCtxKey{}).(*atomic.Bool); ok && v != nil {
|
||||
v.Store(true)
|
||||
}
|
||||
}
|
||||
|
||||
// AuditAlreadyLogged reports whether PostLog (or MarkAuditLogged) has run for r.
|
||||
func AuditAlreadyLogged(r *http.Request) bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
if v, ok := r.Context().Value(auditLogCtxKey{}).(*atomic.Bool); ok && v != nil {
|
||||
return v.Load()
|
||||
}
|
||||
return false
|
||||
}
|
||||
162
weed/s3api/s3err/audit_fluent_test.go
Normal file
162
weed/s3api/s3err/audit_fluent_test.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/request_id"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetAccessLogUsesAmzRequestID(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/bucket/object", nil)
|
||||
req = req.WithContext(request_id.Set(req.Context(), "req-123"))
|
||||
|
||||
log := GetAccessLog(req, http.StatusOK, ErrNone)
|
||||
|
||||
assert.Equal(t, "req-123", log.RequestID)
|
||||
}
|
||||
|
||||
func TestGetAccessLogRemoteIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
remoteAddr string
|
||||
xRealIP string
|
||||
xForwardedFor string
|
||||
expectedRemote string
|
||||
}{
|
||||
{
|
||||
name: "falls back to RemoteAddr (port stripped) when no headers set",
|
||||
remoteAddr: "10.89.0.1:35832",
|
||||
expectedRemote: "10.89.0.1",
|
||||
},
|
||||
{
|
||||
name: "preserves IPv6 host from RemoteAddr",
|
||||
remoteAddr: "[2001:db8::1]:35832",
|
||||
expectedRemote: "2001:db8::1",
|
||||
},
|
||||
{
|
||||
name: "returns RemoteAddr unchanged when no port present",
|
||||
remoteAddr: "@",
|
||||
expectedRemote: "@",
|
||||
},
|
||||
{
|
||||
name: "uses X-Real-IP when X-Forwarded-For is absent",
|
||||
remoteAddr: "10.89.0.1:35832",
|
||||
xRealIP: "203.0.113.7",
|
||||
expectedRemote: "203.0.113.7",
|
||||
},
|
||||
{
|
||||
name: "prefers X-Forwarded-For over X-Real-IP",
|
||||
remoteAddr: "10.89.0.1:35832",
|
||||
xRealIP: "203.0.113.7",
|
||||
xForwardedFor: "198.51.100.42",
|
||||
expectedRemote: "198.51.100.42",
|
||||
},
|
||||
{
|
||||
name: "uses first hop in X-Forwarded-For chain",
|
||||
remoteAddr: "10.89.0.1:35832",
|
||||
xForwardedFor: "198.51.100.42, 10.0.0.5, 10.89.0.1",
|
||||
expectedRemote: "198.51.100.42",
|
||||
},
|
||||
{
|
||||
name: "skips empty leading entries in X-Forwarded-For",
|
||||
remoteAddr: "10.89.0.1:35832",
|
||||
xForwardedFor: ", 198.51.100.42",
|
||||
expectedRemote: "198.51.100.42",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/bucket/object", nil)
|
||||
req.RemoteAddr = tc.remoteAddr
|
||||
if tc.xRealIP != "" {
|
||||
req.Header.Set("X-Real-IP", tc.xRealIP)
|
||||
}
|
||||
if tc.xForwardedFor != "" {
|
||||
req.Header.Set("X-Forwarded-For", tc.xForwardedFor)
|
||||
}
|
||||
|
||||
log := GetAccessLog(req, http.StatusOK, ErrNone)
|
||||
|
||||
assert.Equal(t, tc.expectedRemote, log.RemoteIP)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetAccessLogRequesterFromFallback reproduces the fallback audit path for
|
||||
// GET/HEAD/IAM operations: authentication records the requester on a request
|
||||
// copy the track() middleware never sees, yet the fallback audit entry (built
|
||||
// from the original request) must still report the authenticated user.
|
||||
func TestGetAccessLogRequesterFromFallback(t *testing.T) {
|
||||
// track() installs the holder before authentication runs.
|
||||
outer := s3_constants.EnsureIdentityHolder(httptest.NewRequest(http.MethodGet, "/bucket/object", nil))
|
||||
|
||||
// auth records the identity on a copy and hands that copy to the handler;
|
||||
// the copy itself is discarded once the handler returns.
|
||||
_ = outer.WithContext(s3_constants.SetIdentityNameInContext(outer.Context(), "admin"))
|
||||
|
||||
// The handler returned without logging, so track() builds the fallback entry
|
||||
// from the original request.
|
||||
log := GetAccessLog(outer, http.StatusOK, ErrNone)
|
||||
|
||||
assert.Equal(t, "admin", log.Requester, "fallback audit entry must report the authenticated requester")
|
||||
}
|
||||
|
||||
func TestGetAccessLogRequesterAnonymous(t *testing.T) {
|
||||
req := s3_constants.EnsureIdentityHolder(httptest.NewRequest(http.MethodGet, "/bucket/object", nil))
|
||||
|
||||
log := GetAccessLog(req, http.StatusOK, ErrNone)
|
||||
|
||||
assert.Empty(t, log.Requester, "anonymous request must not report a requester")
|
||||
assert.Empty(t, log.RequesterArn, "anonymous request must not report a principal ARN")
|
||||
}
|
||||
|
||||
// An STS session's identity name is an opaque session subject, so the audit
|
||||
// entry must also carry the principal ARN — that is where the assumed role and
|
||||
// the role session name are recoverable from.
|
||||
func TestGetAccessLogRequesterArnForAssumedRole(t *testing.T) {
|
||||
outer := s3_constants.EnsureIdentityHolder(httptest.NewRequest(http.MethodGet, "/bucket/object", nil))
|
||||
|
||||
// Auth writes into the holder from a request copy the audit path never sees.
|
||||
ctx := s3_constants.SetIdentityNameInContext(outer.Context(), "47ad4828c45b3f337bc3146081ba8f0f")
|
||||
s3_constants.SetPrincipalArnInContext(ctx, "arn:aws:sts::000000000000:assumed-role/ClientRole/dev-session")
|
||||
|
||||
log := GetAccessLog(outer, http.StatusOK, ErrNone)
|
||||
|
||||
assert.Equal(t, "47ad4828c45b3f337bc3146081ba8f0f", log.Requester)
|
||||
assert.Equal(t, "arn:aws:sts::000000000000:assumed-role/ClientRole/dev-session", log.RequesterArn)
|
||||
}
|
||||
|
||||
// An OIDC-federated session's opaque requester is useless for compliance
|
||||
// auditing, so the audit entry must also carry the authoritative identity claim
|
||||
// written by auth into the shared identity holder. See issue #11264.
|
||||
func TestGetAccessLogRequesterIdentityFromFallback(t *testing.T) {
|
||||
outer := s3_constants.EnsureIdentityHolder(httptest.NewRequest(http.MethodGet, "/bucket/object", nil))
|
||||
|
||||
ctx := s3_constants.SetIdentityNameInContext(outer.Context(), "2a19e647c5d43a62a91ecf664d07dbe1")
|
||||
s3_constants.SetIdentityClaimInContext(ctx, "grant.west")
|
||||
|
||||
log := GetAccessLog(outer, http.StatusOK, ErrNone)
|
||||
|
||||
assert.Equal(t, "2a19e647c5d43a62a91ecf664d07dbe1", log.Requester)
|
||||
assert.Equal(t, "grant.west", log.RequesterIdentity)
|
||||
}
|
||||
|
||||
func TestAuditTrackingFlag(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/bucket/object", nil)
|
||||
assert.False(t, AuditAlreadyLogged(req), "untracked request reports not logged")
|
||||
|
||||
tracked := EnsureAuditTracking(req)
|
||||
assert.NotSame(t, req, tracked, "EnsureAuditTracking returns a new request when no flag is present")
|
||||
assert.False(t, AuditAlreadyLogged(tracked), "tracked request starts unlogged")
|
||||
|
||||
again := EnsureAuditTracking(tracked)
|
||||
assert.Same(t, tracked, again, "EnsureAuditTracking is idempotent when flag already present")
|
||||
|
||||
MarkAuditLogged(tracked)
|
||||
assert.True(t, AuditAlreadyLogged(tracked), "flag flips after MarkAuditLogged")
|
||||
}
|
||||
163
weed/s3api/s3err/error_handler.go
Normal file
163
weed/s3api/s3err/error_handler.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/request_id"
|
||||
)
|
||||
|
||||
type mimeType string
|
||||
|
||||
const (
|
||||
mimeNone mimeType = ""
|
||||
MimeXML mimeType = "application/xml"
|
||||
|
||||
errorResponseBodyDrainTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
func WriteAwsXMLResponse(w http.ResponseWriter, r *http.Request, statusCode int, result interface{}) {
|
||||
var bytesBuffer bytes.Buffer
|
||||
err := xmlutil.BuildXML(result, xml.NewEncoder(&bytesBuffer))
|
||||
if err != nil {
|
||||
WriteErrorResponse(w, r, ErrInternalError)
|
||||
return
|
||||
}
|
||||
WriteResponse(w, r, statusCode, bytesBuffer.Bytes(), MimeXML)
|
||||
}
|
||||
|
||||
func WriteXMLResponse(w http.ResponseWriter, r *http.Request, statusCode int, response interface{}) {
|
||||
WriteResponse(w, r, statusCode, EncodeXMLResponse(response), MimeXML)
|
||||
}
|
||||
|
||||
func WriteEmptyResponse(w http.ResponseWriter, r *http.Request, statusCode int) {
|
||||
WriteResponse(w, r, statusCode, []byte{}, mimeNone)
|
||||
PostLog(r, statusCode, ErrNone)
|
||||
}
|
||||
|
||||
func WriteErrorResponse(w http.ResponseWriter, r *http.Request, errorCode ErrorCode) {
|
||||
WriteErrorResponseWithMessage(w, r, errorCode, "")
|
||||
}
|
||||
|
||||
// WriteErrorResponseWithMessage writes an S3 error response that uses the
|
||||
// standard error code mapping (status + Code). When message is non-empty,
|
||||
// it overrides the default Message field so the caller can surface why the
|
||||
// request was rejected (e.g. which POST policy condition failed) instead
|
||||
// of the generic APIError Description.
|
||||
func WriteErrorResponseWithMessage(w http.ResponseWriter, r *http.Request, errorCode ErrorCode, message string) {
|
||||
r, reqID := request_id.Ensure(r)
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object := vars["object"]
|
||||
if strings.HasPrefix(object, "/") {
|
||||
object = object[1:]
|
||||
}
|
||||
|
||||
apiError := GetAPIError(errorCode)
|
||||
errorResponse := getRESTErrorResponse(apiError, r.URL.Path, bucket, object, reqID)
|
||||
if message != "" {
|
||||
errorResponse.Message = message
|
||||
}
|
||||
drainRequestBody(w, r)
|
||||
WriteXMLResponse(w, r, apiError.HTTPStatusCode, errorResponse)
|
||||
PostLog(r, apiError.HTTPStatusCode, errorCode)
|
||||
}
|
||||
|
||||
func drainRequestBody(w http.ResponseWriter, r *http.Request) {
|
||||
if r == nil || r.Body == nil || r.Body == http.NoBody {
|
||||
return
|
||||
}
|
||||
rc := http.NewResponseController(w)
|
||||
if err := rc.SetReadDeadline(time.Now().Add(errorResponseBodyDrainTimeout)); err == nil {
|
||||
defer rc.SetReadDeadline(time.Time{})
|
||||
}
|
||||
_, _ = io.Copy(io.Discard, r.Body)
|
||||
}
|
||||
|
||||
func getRESTErrorResponse(err APIError, resource string, bucket, object, requestID string) RESTErrorResponse {
|
||||
return RESTErrorResponse{
|
||||
Code: err.Code,
|
||||
BucketName: bucket,
|
||||
Key: object,
|
||||
Message: err.Description,
|
||||
Resource: resource,
|
||||
RequestID: requestID,
|
||||
}
|
||||
}
|
||||
|
||||
// Encodes the response headers into XML format.
|
||||
func EncodeXMLResponse(response interface{}) []byte {
|
||||
var bytesBuffer bytes.Buffer
|
||||
bytesBuffer.WriteString(xml.Header)
|
||||
e := xml.NewEncoder(&bytesBuffer)
|
||||
e.Encode(response)
|
||||
return bytesBuffer.Bytes()
|
||||
}
|
||||
|
||||
func setCommonHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
_, reqID := request_id.Ensure(r)
|
||||
w.Header().Set(request_id.AmzRequestIDHeader, reqID)
|
||||
w.Header().Set("Accept-Ranges", "bytes")
|
||||
|
||||
// Handle CORS headers for requests with Origin header
|
||||
if r.Header.Get("Origin") != "" {
|
||||
// Use mux.Vars to detect bucket-specific requests more reliably
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
isBucketRequest := bucket != ""
|
||||
|
||||
if !isBucketRequest {
|
||||
// Service-level request (like OPTIONS /) - apply static CORS if none set
|
||||
if w.Header().Get("Access-Control-Allow-Origin") == "" {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "*")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||
w.Header().Set("Access-Control-Expose-Headers", "*")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
} else {
|
||||
// Bucket-specific request - preserve existing CORS headers or set default
|
||||
// This handles cases where CORS middleware set headers but auth failed
|
||||
if w.Header().Get("Access-Control-Allow-Origin") == "" {
|
||||
// No CORS headers were set by middleware, so this request doesn't match any CORS rule
|
||||
// According to CORS spec, we should not set CORS headers for non-matching requests
|
||||
// However, if the bucket has CORS config but request doesn't match,
|
||||
// we still should not set headers here as it would be incorrect
|
||||
}
|
||||
// If CORS headers were already set by middleware, preserve them
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WriteResponse(w http.ResponseWriter, r *http.Request, statusCode int, response []byte, mType mimeType) {
|
||||
setCommonHeaders(w, r)
|
||||
if response != nil {
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||
}
|
||||
if mType != mimeNone {
|
||||
w.Header().Set("Content-Type", string(mType))
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
if response != nil {
|
||||
glog.V(4).Infof("status %d %s: %s", statusCode, mType, string(response))
|
||||
_, err := w.Write(response)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("write err: %v", err)
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the http routes match respond with MethodNotAllowed
|
||||
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
glog.V(2).Infof("unsupported %s %s", r.Method, r.RequestURI)
|
||||
WriteErrorResponse(w, r, ErrMethodNotAllowed)
|
||||
}
|
||||
118
weed/s3api/s3err/error_handler_test.go
Normal file
118
weed/s3api/s3err/error_handler_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/request_id"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWriteErrorResponseReusesRequestID(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/bucket/object", nil)
|
||||
req = mux.SetURLVars(req, map[string]string{
|
||||
"bucket": "bucket",
|
||||
"object": "object",
|
||||
})
|
||||
req = req.WithContext(request_id.Set(req.Context(), "req-123"))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
WriteErrorResponse(rr, req, ErrNoSuchKey)
|
||||
|
||||
assert.Equal(t, "req-123", rr.Header().Get(request_id.AmzRequestIDHeader))
|
||||
assert.Equal(t, "req-123", extractRequestIDFromBody(rr.Body.String()))
|
||||
}
|
||||
|
||||
func TestWriteErrorResponseDrainsRequestBodyBeforeWriting(t *testing.T) {
|
||||
body := &trackingReadCloser{data: bytes.Repeat([]byte("a"), 1024)}
|
||||
req := httptest.NewRequest(http.MethodPut, "/bucket/object", nil)
|
||||
req.Body = body
|
||||
req.ContentLength = int64(body.remaining())
|
||||
req = mux.SetURLVars(req, map[string]string{
|
||||
"bucket": "bucket",
|
||||
"object": "object",
|
||||
})
|
||||
|
||||
rr := &drainCheckingResponseWriter{
|
||||
header: make(http.Header),
|
||||
body: body,
|
||||
}
|
||||
|
||||
WriteErrorResponse(rr, req, ErrInternalError)
|
||||
|
||||
assert.Empty(t, rr.writeHeaderErr)
|
||||
assert.Equal(t, 0, body.remaining())
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.status)
|
||||
assert.Len(t, rr.readDeadlines, 2)
|
||||
assert.False(t, rr.readDeadlines[0].IsZero())
|
||||
assert.True(t, rr.readDeadlines[1].IsZero())
|
||||
}
|
||||
|
||||
func extractRequestIDFromBody(body string) string {
|
||||
re := regexp.MustCompile(`<RequestId>([^<]+)</RequestId>`)
|
||||
matches := re.FindStringSubmatch(body)
|
||||
if len(matches) < 2 {
|
||||
return ""
|
||||
}
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
type trackingReadCloser struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (t *trackingReadCloser) Read(p []byte) (int, error) {
|
||||
if len(t.data) == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n := copy(p, t.data)
|
||||
t.data = t.data[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (t *trackingReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *trackingReadCloser) remaining() int {
|
||||
return len(t.data)
|
||||
}
|
||||
|
||||
type drainCheckingResponseWriter struct {
|
||||
header http.Header
|
||||
body *trackingReadCloser
|
||||
status int
|
||||
writeHeaderErr string
|
||||
readDeadlines []time.Time
|
||||
}
|
||||
|
||||
func (d *drainCheckingResponseWriter) Header() http.Header {
|
||||
return d.header
|
||||
}
|
||||
|
||||
func (d *drainCheckingResponseWriter) Write(p []byte) (int, error) {
|
||||
if d.status == 0 {
|
||||
d.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (d *drainCheckingResponseWriter) WriteHeader(status int) {
|
||||
d.status = status
|
||||
if d.body.remaining() != 0 {
|
||||
d.writeHeaderErr = "request body was not drained before WriteHeader"
|
||||
}
|
||||
}
|
||||
|
||||
func (d *drainCheckingResponseWriter) Flush() {}
|
||||
|
||||
func (d *drainCheckingResponseWriter) SetReadDeadline(t time.Time) error {
|
||||
d.readDeadlines = append(d.readDeadlines, t)
|
||||
return nil
|
||||
}
|
||||
64
weed/s3api/s3err/s3-error.go
Normal file
64
weed/s3api/s3err/s3-error.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package s3err
|
||||
|
||||
import "github.com/seaweedfs/seaweedfs/weed/util/constants"
|
||||
|
||||
/*
|
||||
* MinIO Go Library for Amazon S3 Compatible Cloud Storage
|
||||
* Copyright 2015-2017 MinIO, Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Non exhaustive list of AWS S3 standard error responses -
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||
var s3ErrorResponseMap = map[string]string{
|
||||
"AccessDenied": "Access Denied.",
|
||||
"BadDigest": constants.ErrMsgBadDigest,
|
||||
"EntityTooSmall": "Your proposed upload is smaller than the minimum allowed object size.",
|
||||
"EntityTooLarge": "Your proposed upload exceeds the maximum allowed object size.",
|
||||
"IncompleteBody": "You did not provide the number of bytes specified by the Content-Length HTTP header.",
|
||||
"InternalError": "We encountered an internal error, please try again.",
|
||||
"InvalidAccessKeyId": "The access key ID you provided does not exist in our records.",
|
||||
"InvalidBucketName": "The specified bucket is not valid.",
|
||||
"InvalidDigest": "The Content-Md5 you specified is not valid.",
|
||||
"InvalidRange": "The requested range is not satisfiable",
|
||||
"MalformedXML": "The XML you provided was not well-formed or did not validate against our published schema.",
|
||||
"MissingContentLength": "You must provide the Content-Length HTTP header.",
|
||||
"MissingContentMD5": "Missing required header for this request: Content-Md5.",
|
||||
"MissingRequestBodyError": "Request body is empty.",
|
||||
"NoSuchBucket": "The specified bucket does not exist.",
|
||||
"NoSuchBucketPolicy": "The bucket policy does not exist",
|
||||
"NoSuchKey": "The specified key does not exist.",
|
||||
"NoSuchUpload": "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
|
||||
"NotImplemented": "A header you provided implies functionality that is not implemented",
|
||||
"PreconditionFailed": "At least one of the pre-conditions you specified did not hold",
|
||||
"RequestTimeTooSkewed": "The difference between the request time and the server's time is too large.",
|
||||
"SignatureDoesNotMatch": "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
|
||||
"MethodNotAllowed": "The specified method is not allowed against this resource.",
|
||||
"InvalidPart": "One or more of the specified parts could not be found.",
|
||||
"InvalidPartOrder": "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
||||
"InvalidObjectState": "The operation is not valid for the current state of the object.",
|
||||
"AuthorizationHeaderMalformed": "The authorization header is malformed; the region is wrong.",
|
||||
"MalformedPOSTRequest": "The body of your POST request is not well-formed multipart/form-data.",
|
||||
"BucketNotEmpty": "The bucket you tried to delete is not empty",
|
||||
"AllAccessDisabled": "All access to this bucket has been disabled.",
|
||||
"MalformedPolicy": "Policy has invalid resource.",
|
||||
"MissingFields": "Missing fields in request.",
|
||||
"AuthorizationQueryParametersError": "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".",
|
||||
"MalformedDate": "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
|
||||
"BucketAlreadyOwnedByYou": "Your previous request to create the named bucket succeeded and you already own it.",
|
||||
"InvalidDuration": "Duration provided in the request is invalid.",
|
||||
"XAmzContentSHA256Mismatch": "The provided 'x-amz-content-sha256' header does not match what was computed.",
|
||||
// Add new API errors here.
|
||||
"NoSuchCORSConfiguration": "The CORS configuration does not exist",
|
||||
}
|
||||
735
weed/s3api/s3err/s3api_errors.go
Normal file
735
weed/s3api/s3err/s3api_errors.go
Normal file
@@ -0,0 +1,735 @@
|
||||
package s3err
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/constants"
|
||||
)
|
||||
|
||||
// APIError structure
|
||||
type APIError struct {
|
||||
Code string
|
||||
Description string
|
||||
HTTPStatusCode int
|
||||
}
|
||||
|
||||
// RESTErrorResponse - error response format
|
||||
type RESTErrorResponse struct {
|
||||
XMLName xml.Name `xml:"Error" json:"-"`
|
||||
Code string `xml:"Code" json:"Code"`
|
||||
Message string `xml:"Message" json:"Message"`
|
||||
Resource string `xml:"Resource" json:"Resource"`
|
||||
RequestID string `xml:"RequestId" json:"RequestId"`
|
||||
Key string `xml:"Key,omitempty" json:"Key,omitempty"`
|
||||
BucketName string `xml:"BucketName,omitempty" json:"BucketName,omitempty"`
|
||||
|
||||
// Underlying HTTP status code for the returned error
|
||||
StatusCode int `xml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// Error - Returns S3 error string.
|
||||
func (e RESTErrorResponse) Error() string {
|
||||
if e.Message == "" {
|
||||
msg, ok := s3ErrorResponseMap[e.Code]
|
||||
if !ok {
|
||||
msg = fmt.Sprintf("Error response code %s.", e.Code)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// ErrorCode type of error status.
|
||||
type ErrorCode int
|
||||
|
||||
// Error codes, see full list at http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
||||
const (
|
||||
ErrNone ErrorCode = iota
|
||||
ErrAccessDenied
|
||||
ErrMethodNotAllowed
|
||||
ErrBucketNotEmpty
|
||||
ErrBucketAlreadyExists
|
||||
ErrBucketAlreadyOwnedByYou
|
||||
ErrNoSuchBucket
|
||||
ErrNoSuchBucketPolicy
|
||||
ErrNoSuchCORSConfiguration
|
||||
ErrNoSuchLifecycleConfiguration
|
||||
ErrNoSuchWebsiteConfiguration
|
||||
ErrReplicationConfigurationNotFound
|
||||
ErrNoSuchKey
|
||||
ErrNoSuchVersion
|
||||
ErrNoSuchUpload
|
||||
ErrInvalidBucketName
|
||||
ErrInvalidBucketState
|
||||
ErrInvalidDigest
|
||||
ErrBadDigest
|
||||
ErrInvalidMaxKeys
|
||||
ErrInvalidMaxBuckets
|
||||
ErrInvalidContinuationToken
|
||||
ErrInvalidMaxUploads
|
||||
ErrInvalidMaxParts
|
||||
ErrInvalidMaxDeleteObjects
|
||||
ErrInvalidPartNumberMarker
|
||||
ErrInvalidPart
|
||||
ErrInvalidPartNumber
|
||||
ErrInvalidPartOrder
|
||||
ErrInvalidRange
|
||||
ErrInternalError
|
||||
ErrInvalidCopyDest
|
||||
ErrInvalidCopySource
|
||||
ErrInvalidTag
|
||||
ErrAuthHeaderEmpty
|
||||
ErrSignatureVersionNotSupported
|
||||
ErrMalformedPOSTRequest
|
||||
ErrPOSTFileRequired
|
||||
ErrPostPolicyConditionInvalidFormat
|
||||
ErrEntityTooSmall
|
||||
ErrEntityTooLarge
|
||||
ErrMissingFields
|
||||
ErrMissingCredTag
|
||||
ErrCredMalformed
|
||||
ErrMalformedXML
|
||||
ErrMalformedDate
|
||||
ErrMalformedPresignedDate
|
||||
ErrMalformedCredentialDate
|
||||
ErrMalformedPolicy
|
||||
ErrInvalidPolicyDocument
|
||||
ErrPolicyTooLarge
|
||||
ErrMissingSignHeadersTag
|
||||
ErrMissingSignTag
|
||||
ErrUnsignedHeaders
|
||||
ErrInvalidQueryParams
|
||||
ErrInvalidQuerySignatureAlgo
|
||||
ErrExpiredPresignRequest
|
||||
ErrExpiredToken
|
||||
ErrMalformedExpires
|
||||
ErrNegativeExpires
|
||||
ErrMaximumExpires
|
||||
ErrSignatureDoesNotMatch
|
||||
ErrContentSHA256Mismatch
|
||||
ErrInvalidAccessKeyID
|
||||
ErrRequestNotReadyYet
|
||||
ErrRequestTimeTooSkewed
|
||||
ErrMissingDateHeader
|
||||
ErrInvalidRequest
|
||||
ErrAuthNotSetup
|
||||
ErrNotImplemented
|
||||
ErrPreconditionFailed
|
||||
ErrNotModified
|
||||
|
||||
ErrExistingObjectIsDirectory
|
||||
ErrExistingObjectIsFile
|
||||
|
||||
ErrTooManyRequest
|
||||
ErrRequestBytesExceed
|
||||
ErrServiceUnavailable
|
||||
|
||||
OwnershipControlsNotFoundError
|
||||
ErrNoSuchTagSet
|
||||
ErrNoSuchObjectLockConfiguration
|
||||
ErrNoSuchObjectLegalHold
|
||||
ErrInvalidRetentionPeriod
|
||||
ErrObjectLockConfigurationNotFoundError
|
||||
ErrInvalidUnorderedWithDelimiter
|
||||
|
||||
// SSE-C related errors
|
||||
ErrInvalidEncryptionAlgorithm
|
||||
ErrInvalidEncryptionKey
|
||||
ErrSSECustomerKeyMD5Mismatch
|
||||
ErrSSECustomerKeyMissing
|
||||
ErrSSECustomerKeyNotNeeded
|
||||
ErrSSEEncryptionTypeMismatch
|
||||
|
||||
// SSE-KMS related errors
|
||||
ErrKMSKeyNotFound
|
||||
ErrKMSAccessDenied
|
||||
ErrKMSDisabled
|
||||
ErrKMSInvalidCiphertext
|
||||
|
||||
// Bucket encryption errors
|
||||
ErrNoSuchBucketEncryptionConfiguration
|
||||
ErrInvalidStorageClass
|
||||
|
||||
ErrInvalidMetadataDirective
|
||||
ErrInvalidTagDirective
|
||||
|
||||
ErrInvalidAttributeName
|
||||
|
||||
// Object key length errors
|
||||
ErrKeyTooLongError
|
||||
|
||||
ErrNoSuchConfiguration
|
||||
|
||||
// Truncated request body (fewer bytes than Content-Length)
|
||||
ErrIncompleteBody
|
||||
|
||||
// Peer went away before the request body was fully received
|
||||
ErrClientDisconnected
|
||||
|
||||
ErrInvalidRenameSource
|
||||
ErrRenameDestinationSameAsSource
|
||||
ErrIdempotentParameterMismatch
|
||||
)
|
||||
|
||||
// Error message constants for checksum validation
|
||||
const (
|
||||
ErrMsgPayloadChecksumMismatch = "payload checksum does not match"
|
||||
ErrMsgChunkSignatureMismatch = "chunk signature does not match"
|
||||
ErrMsgChecksumAlgorithmMismatch = "checksum algorithm mismatch"
|
||||
)
|
||||
|
||||
// error code to APIError structure, these fields carry respective
|
||||
// descriptions for all the error responses.
|
||||
var errorCodeResponse = map[ErrorCode]APIError{
|
||||
ErrAccessDenied: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Access Denied.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrMethodNotAllowed: {
|
||||
Code: "MethodNotAllowed",
|
||||
Description: "The specified method is not allowed against this resource.",
|
||||
HTTPStatusCode: http.StatusMethodNotAllowed,
|
||||
},
|
||||
ErrBucketNotEmpty: {
|
||||
Code: "BucketNotEmpty",
|
||||
Description: "The bucket you tried to delete is not empty",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
ErrBucketAlreadyExists: {
|
||||
Code: "BucketAlreadyExists",
|
||||
Description: "The requested bucket name is not available. The bucket name can not be an existing collection, and the bucket namespace is shared by all users of the system. Please select a different name and try again.",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
ErrBucketAlreadyOwnedByYou: {
|
||||
Code: "BucketAlreadyOwnedByYou",
|
||||
Description: "Your previous request to create the named bucket succeeded and you already own it.",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
ErrInvalidBucketName: {
|
||||
Code: "InvalidBucketName",
|
||||
Description: "The specified bucket is not valid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidBucketState: {
|
||||
Code: "InvalidBucketState",
|
||||
Description: "The bucket is not in a valid state for the requested operation",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
ErrInvalidDigest: {
|
||||
Code: "InvalidDigest",
|
||||
Description: "The Content-Md5 you specified is not valid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrBadDigest: {
|
||||
Code: "BadDigest",
|
||||
Description: constants.ErrMsgBadDigest,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidMaxUploads: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument max-uploads must be an integer between 0 and 2147483647",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidMaxKeys: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument maxKeys must be an integer between 0 and 2147483647",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidMaxBuckets: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument max-buckets must be an integer between 1 and 10000",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidContinuationToken: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The continuation token provided is incorrect",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidMaxParts: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument max-parts must be an integer between 0 and 2147483647",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidMaxDeleteObjects: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument objects can contain a list of up to 1000 keys",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidPartNumberMarker: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Argument partNumberMarker must be an integer.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNoSuchBucket: {
|
||||
Code: "NoSuchBucket",
|
||||
Description: "The specified bucket does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchBucketPolicy: {
|
||||
Code: "NoSuchBucketPolicy",
|
||||
Description: "The bucket policy does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchTagSet: {
|
||||
Code: "NoSuchTagSet",
|
||||
Description: "The TagSet does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchObjectLockConfiguration: {
|
||||
Code: "NoSuchObjectLockConfiguration",
|
||||
Description: "The specified object does not have an ObjectLock configuration",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchObjectLegalHold: {
|
||||
Code: "NoSuchObjectLegalHold",
|
||||
Description: "The specified object does not have a legal hold configuration",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrInvalidRetentionPeriod: {
|
||||
Code: "InvalidRetentionPeriod",
|
||||
Description: "The retention period specified is invalid",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNoSuchCORSConfiguration: {
|
||||
Code: "NoSuchCORSConfiguration",
|
||||
Description: "The CORS configuration does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchLifecycleConfiguration: {
|
||||
Code: "NoSuchLifecycleConfiguration",
|
||||
Description: "The lifecycle configuration does not exist",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchWebsiteConfiguration: {
|
||||
Code: "NoSuchWebsiteConfiguration",
|
||||
Description: "The specified bucket does not have a website configuration",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrReplicationConfigurationNotFound: {
|
||||
Code: "ReplicationConfigurationNotFoundError",
|
||||
Description: "The replication configuration was not found",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchKey: {
|
||||
Code: "NoSuchKey",
|
||||
Description: "The specified key does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchVersion: {
|
||||
Code: "NoSuchVersion",
|
||||
Description: "The specified version does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchUpload: {
|
||||
Code: "NoSuchUpload",
|
||||
Description: "The specified multipart upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrInternalError: {
|
||||
Code: "InternalError",
|
||||
Description: "We encountered an internal error, please try again.",
|
||||
HTTPStatusCode: http.StatusInternalServerError,
|
||||
},
|
||||
ErrIncompleteBody: {
|
||||
Code: "IncompleteBody",
|
||||
Description: "You did not provide the number of bytes specified by the Content-Length HTTP header.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
// 499 has no RFC; it is nginx's code for a client that went away, and is what
|
||||
// log pipelines already recognise for this case.
|
||||
ErrClientDisconnected: {
|
||||
Code: "ClientDisconnected",
|
||||
Description: "The client disconnected before the request body was fully received.",
|
||||
HTTPStatusCode: 499,
|
||||
},
|
||||
|
||||
ErrInvalidPart: {
|
||||
Code: "InvalidPart",
|
||||
Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidPartNumber: {
|
||||
Code: "InvalidPartNumber",
|
||||
Description: "The requested partnumber is not satisfiable.",
|
||||
HTTPStatusCode: http.StatusRequestedRangeNotSatisfiable,
|
||||
},
|
||||
ErrInvalidPartOrder: {
|
||||
Code: "InvalidPartOrder",
|
||||
Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrInvalidCopyDest: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidCopySource: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Copy Source must mention the source bucket and key: sourcebucket/sourcekey.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRenameSource: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Rename Source must mention the source bucket and key: sourcebucket/sourcekey.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrRenameDestinationSameAsSource: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "This rename request is illegal because it is trying to rename an object to itself.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrIdempotentParameterMismatch: {
|
||||
Code: "IdempotentParameterMismatch",
|
||||
Description: "The request uses the same client token as a previous, but non-identical request.",
|
||||
// 400 Bad Request, matching the AWS S3 RenameObject API documentation
|
||||
// for IdempotencyParameterMismatch.
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidTag: {
|
||||
Code: "InvalidTag",
|
||||
Description: "The Tag value you have provided is invalid",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedXML: {
|
||||
Code: "MalformedXML",
|
||||
Description: "The XML you provided was not well-formed or did not validate against our published schema.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedPolicy: {
|
||||
Code: "MalformedPolicy",
|
||||
Description: "Policy has invalid resource.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPolicyTooLarge: {
|
||||
Code: "PolicyTooLarge",
|
||||
Description: "Policy exceeds the maximum allowed document size.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidPolicyDocument: {
|
||||
Code: "InvalidPolicyDocument",
|
||||
Description: "The content of the policy document is invalid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrAuthHeaderEmpty: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Authorization header is invalid -- one and only one ' ' (space) required.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrSignatureVersionNotSupported: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedPOSTRequest: {
|
||||
Code: "MalformedPOSTRequest",
|
||||
Description: "The body of your POST request is not well-formed multipart/form-data.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPOSTFileRequired: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "POST requires exactly one file upload per request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPostPolicyConditionInvalidFormat: {
|
||||
Code: "PostPolicyInvalidKeyName",
|
||||
Description: "Invalid according to Policy: Policy Condition failed",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrEntityTooSmall: {
|
||||
Code: "EntityTooSmall",
|
||||
Description: "Your proposed upload is smaller than the minimum allowed object size.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrEntityTooLarge: {
|
||||
Code: "EntityTooLarge",
|
||||
Description: "Your proposed upload exceeds the maximum allowed object size.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingFields: {
|
||||
Code: "MissingFields",
|
||||
Description: "Missing fields in request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingCredTag: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Missing Credential field for this request.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrCredMalformed: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"<YOUR-AKID>/YYYYMMDD/REGION/SERVICE/aws4_request\".",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedDate: {
|
||||
Code: "MalformedDate",
|
||||
Description: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedPresignedDate: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "X-Amz-Date must be in the ISO8601 Long Format \"yyyyMMdd'T'HHmmss'Z'\"",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingSignHeadersTag: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Signature header missing SignedHeaders field.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingSignTag: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Signature header missing Signature field.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrUnsignedHeaders: {
|
||||
Code: "AccessDenied",
|
||||
Description: "There were headers present in the request which were not signed",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidQueryParams: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidQuerySignatureAlgo: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\".",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrExpiredPresignRequest: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Request has expired",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrExpiredToken: {
|
||||
Code: "ExpiredToken",
|
||||
Description: "The provided token has expired.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMalformedExpires: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "X-Amz-Expires should be a number",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNegativeExpires: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "X-Amz-Expires must be non-negative",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMaximumExpires: {
|
||||
Code: "AuthorizationQueryParametersError",
|
||||
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrInvalidAccessKeyID: {
|
||||
Code: "InvalidAccessKeyId",
|
||||
Description: "The access key ID you provided does not exist in our records.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
|
||||
ErrRequestNotReadyYet: {
|
||||
Code: "AccessDenied",
|
||||
Description: "Request is not valid yet",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
|
||||
ErrRequestTimeTooSkewed: {
|
||||
Code: "RequestTimeTooSkewed",
|
||||
Description: "The difference between the request time and the server's time is too large.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
|
||||
ErrSignatureDoesNotMatch: {
|
||||
Code: "SignatureDoesNotMatch",
|
||||
Description: "The request signature we calculated does not match the signature you provided. Check your key and signing method.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
|
||||
ErrContentSHA256Mismatch: {
|
||||
Code: "XAmzContentSHA256Mismatch",
|
||||
Description: "The provided 'x-amz-content-sha256' header does not match what was computed.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrMissingDateHeader: {
|
||||
Code: "AccessDenied",
|
||||
Description: "AWS authentication requires a valid Date or x-amz-date header",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRequest: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Invalid Request",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidRange: {
|
||||
Code: "InvalidRange",
|
||||
Description: "The requested range is not satisfiable",
|
||||
HTTPStatusCode: http.StatusRequestedRangeNotSatisfiable,
|
||||
},
|
||||
ErrAuthNotSetup: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Signed request requires setting up SeaweedFS S3 authentication",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrNotImplemented: {
|
||||
Code: "NotImplemented",
|
||||
Description: "A header you provided implies functionality that is not implemented",
|
||||
HTTPStatusCode: http.StatusNotImplemented,
|
||||
},
|
||||
ErrPreconditionFailed: {
|
||||
Code: "PreconditionFailed",
|
||||
Description: "At least one of the pre-conditions you specified did not hold",
|
||||
HTTPStatusCode: http.StatusPreconditionFailed,
|
||||
},
|
||||
ErrNotModified: {
|
||||
Code: "NotModified",
|
||||
Description: "The object was not modified since the specified time",
|
||||
HTTPStatusCode: http.StatusNotModified,
|
||||
},
|
||||
ErrExistingObjectIsDirectory: {
|
||||
Code: "ExistingObjectIsDirectory",
|
||||
Description: "Existing Object is a directory.",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
ErrExistingObjectIsFile: {
|
||||
Code: "ExistingObjectIsFile",
|
||||
Description: "Existing Object is a file.",
|
||||
HTTPStatusCode: http.StatusConflict,
|
||||
},
|
||||
ErrTooManyRequest: {
|
||||
Code: "ErrTooManyRequest",
|
||||
Description: "Too many simultaneous request count",
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
ErrRequestBytesExceed: {
|
||||
Code: "ErrRequestBytesExceed",
|
||||
Description: "Simultaneous request bytes exceed limitations",
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
ErrServiceUnavailable: {
|
||||
Code: "ServiceUnavailable",
|
||||
Description: "Service Unavailable",
|
||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
|
||||
OwnershipControlsNotFoundError: {
|
||||
Code: "OwnershipControlsNotFoundError",
|
||||
Description: "The bucket ownership controls were not found",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrObjectLockConfigurationNotFoundError: {
|
||||
Code: "ObjectLockConfigurationNotFoundError",
|
||||
Description: "Object Lock configuration does not exist for this bucket",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrInvalidUnorderedWithDelimiter: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Unordered listing cannot be used with delimiter",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// SSE-C related error mappings
|
||||
ErrInvalidEncryptionAlgorithm: {
|
||||
Code: "InvalidEncryptionAlgorithmError",
|
||||
Description: "The encryption algorithm specified is not valid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidEncryptionKey: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid encryption key. Encryption key must be 256-bit AES256.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrSSECustomerKeyMD5Mismatch: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The provided customer encryption key MD5 does not match the key.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrSSECustomerKeyMissing: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide the customer key.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrSSECustomerKeyNotNeeded: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "The object was not encrypted with customer provided keys.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrSSEEncryptionTypeMismatch: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "The encryption method specified in the request does not match the encryption method used to encrypt the object.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// SSE-KMS error responses
|
||||
ErrKMSKeyNotFound: {
|
||||
Code: "KMSKeyNotFoundException",
|
||||
Description: "The specified KMS key does not exist.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrKMSAccessDenied: {
|
||||
Code: "KMSAccessDeniedException",
|
||||
Description: "Access denied to the specified KMS key.",
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
},
|
||||
ErrKMSDisabled: {
|
||||
Code: "KMSKeyDisabledException",
|
||||
Description: "The specified KMS key is disabled.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrKMSInvalidCiphertext: {
|
||||
Code: "InvalidCiphertext",
|
||||
Description: "The provided ciphertext is invalid or corrupted.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// Bucket encryption error responses
|
||||
ErrNoSuchBucketEncryptionConfiguration: {
|
||||
Code: "ServerSideEncryptionConfigurationNotFoundError",
|
||||
Description: "The server side encryption configuration was not found.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrInvalidStorageClass: {
|
||||
Code: "InvalidStorageClass",
|
||||
Description: "The storage class you specified is not valid",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrInvalidMetadataDirective: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Unknown metadata directive.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrInvalidTagDirective: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Unknown tag directive.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrInvalidAttributeName: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Invalid attribute name specified",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrKeyTooLongError: {
|
||||
Code: "KeyTooLongError",
|
||||
Description: "Your key is too long.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
ErrNoSuchConfiguration: {
|
||||
Code: "NoSuchConfiguration",
|
||||
Description: "The specified configuration does not exist.",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
// GetAPIError provides API Error for input API error code.
|
||||
func GetAPIError(code ErrorCode) APIError {
|
||||
return errorCodeResponse[code]
|
||||
}
|
||||
Reference in New Issue
Block a user