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
480 lines
18 KiB
Go
480 lines
18 KiB
Go
package s3api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"hash/crc32"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// getDefaultTimestamp returns a current timestamp for tests
|
|
func getDefaultTimestamp() string {
|
|
return time.Now().UTC().Format(iso8601Format)
|
|
}
|
|
|
|
const (
|
|
defaultTimestamp = "20130524T000000Z" // Legacy constant for reference
|
|
defaultBucketName = "examplebucket"
|
|
defaultAccessKeyId = UserAccessKeyPrefix + "IOSFODNN7EXAMPLE"
|
|
defaultSecretAccessKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
defaultRegion = "us-east-1"
|
|
)
|
|
|
|
func TestExtractChecksumAlgorithmIsCaseInsensitive(t *testing.T) {
|
|
got, err := extractChecksumAlgorithm("X-Amz-Checksum-Crc32")
|
|
if err != nil {
|
|
t.Fatalf("extractChecksumAlgorithm returned error: %v", err)
|
|
}
|
|
if got != ChecksumAlgorithmCRC32 {
|
|
t.Fatalf("extractChecksumAlgorithm() = %v, want %v", got, ChecksumAlgorithmCRC32)
|
|
}
|
|
}
|
|
|
|
func generateStreamingUnsignedPayloadTrailerPayload(includeFinalCRLF bool) string {
|
|
// This test will implement the following scenario:
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html
|
|
|
|
chunk1 := "2000\r\n" + strings.Repeat("a", 8192) + "\r\n"
|
|
chunk2 := "2000\r\n" + strings.Repeat("a", 8192) + "\r\n"
|
|
chunk3 := "400\r\n" + strings.Repeat("a", 1024) + "\r\n"
|
|
|
|
chunk4 := "0\r\n" /* the last chunk is empty */
|
|
|
|
if includeFinalCRLF {
|
|
// Some clients omit the final CRLF, so we need to test that case as well
|
|
chunk4 += "\r\n"
|
|
}
|
|
|
|
data := strings.Repeat("a", 17408)
|
|
writer := crc32.NewIEEE()
|
|
_, err := writer.Write([]byte(data))
|
|
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
checksum := writer.Sum(nil)
|
|
base64EncodedChecksum := base64.StdEncoding.EncodeToString(checksum)
|
|
trailer := "x-amz-checksum-crc32:" + base64EncodedChecksum + "\n\r\n\r\n\r\n"
|
|
|
|
payload := chunk1 + chunk2 + chunk3 + chunk4 + trailer
|
|
return payload
|
|
}
|
|
|
|
func NewRequestStreamingUnsignedPayloadTrailer(includeFinalCRLF bool) (*http.Request, error) {
|
|
// This test will implement the following scenario:
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html
|
|
|
|
payload := generateStreamingUnsignedPayloadTrailerPayload(includeFinalCRLF)
|
|
req, err := http.NewRequest("PUT", "http://amzn-s3-demo-bucket/Key+", bytes.NewReader([]byte(payload)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Set("Host", "amzn-s3-demo-bucket")
|
|
req.Header.Set("x-amz-date", getDefaultTimestamp())
|
|
req.Header.Set("Content-Encoding", "aws-chunked")
|
|
req.Header.Set("x-amz-decoded-content-length", "17408")
|
|
req.Header.Set("x-amz-content-sha256", "STREAMING-UNSIGNED-PAYLOAD-TRAILER")
|
|
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32")
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func TestNewSignV4ChunkedReaderStreamingUnsignedPayloadTrailer(t *testing.T) {
|
|
// This test will implement the following scenario:
|
|
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html
|
|
iam := setupIam()
|
|
|
|
req, err := NewRequestStreamingUnsignedPayloadTrailer(true)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
// The expected payload a long string of 'a's
|
|
expectedPayload := strings.Repeat("a", 17408)
|
|
|
|
runWithRequest(iam, req, t, expectedPayload)
|
|
|
|
req, err = NewRequestStreamingUnsignedPayloadTrailer(false)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
runWithRequest(iam, req, t, expectedPayload)
|
|
}
|
|
|
|
func runWithRequest(iam IdentityAccessManagement, req *http.Request, t *testing.T, expectedPayload string) {
|
|
reader, errCode := iam.newChunkedReader(req)
|
|
assert.NotNil(t, reader)
|
|
assert.Equal(t, s3err.ErrNone, errCode)
|
|
|
|
data, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read data: %v", err)
|
|
}
|
|
|
|
assert.Equal(t, expectedPayload, string(data))
|
|
}
|
|
|
|
func setupIam() IdentityAccessManagement {
|
|
// Create an IdentityAccessManagement instance
|
|
// Add default access keys and secrets
|
|
|
|
iam := IdentityAccessManagement{
|
|
identities: []*Identity{},
|
|
accessKeyIdent: map[string]*Identity{},
|
|
accounts: map[string]*Account{},
|
|
emailAccount: map[string]*Account{},
|
|
hashes: map[string]*sync.Pool{},
|
|
hashCounters: map[string]*int32{},
|
|
identityAnonymous: nil,
|
|
domain: "",
|
|
isAuthEnabled: false,
|
|
}
|
|
|
|
iam.identities = append(iam.identities, &Identity{
|
|
Name: "default",
|
|
Credentials: []*Credential{
|
|
{
|
|
AccessKey: defaultAccessKeyId,
|
|
SecretKey: defaultSecretAccessKey,
|
|
},
|
|
},
|
|
Actions: []Action{
|
|
"Read",
|
|
"Write",
|
|
"List",
|
|
},
|
|
})
|
|
|
|
iam.accessKeyIdent[defaultAccessKeyId] = iam.identities[0]
|
|
return iam
|
|
}
|
|
|
|
// TestSignedStreamingUpload tests streaming uploads with signed chunks
|
|
// This replaces the removed AWS example test with a dynamic signature generation approach
|
|
func TestSignedStreamingUpload(t *testing.T) {
|
|
iam := setupIam()
|
|
|
|
// Create a simple streaming upload with 2 chunks
|
|
chunk1Data := strings.Repeat("a", 1024)
|
|
chunk2Data := strings.Repeat("b", 512)
|
|
|
|
// Use current time for signatures
|
|
now := time.Now().UTC()
|
|
amzDate := now.Format(iso8601Format)
|
|
dateStamp := now.Format(yyyymmdd)
|
|
|
|
// Calculate seed signature
|
|
scope := dateStamp + "/" + defaultRegion + "/s3/aws4_request"
|
|
|
|
// Build canonical request for seed signature
|
|
hashedPayload := "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
|
|
canonicalHeaders := "content-encoding:aws-chunked\n" +
|
|
"host:s3.amazonaws.com\n" +
|
|
"x-amz-content-sha256:" + hashedPayload + "\n" +
|
|
"x-amz-date:" + amzDate + "\n" +
|
|
"x-amz-decoded-content-length:1536\n"
|
|
signedHeaders := "content-encoding;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length"
|
|
|
|
canonicalRequest := "PUT\n" +
|
|
"/test-bucket/test-object\n" +
|
|
"\n" +
|
|
canonicalHeaders + "\n" +
|
|
signedHeaders + "\n" +
|
|
hashedPayload
|
|
|
|
canonicalRequestHash := getSHA256Hash([]byte(canonicalRequest))
|
|
stringToSign := "AWS4-HMAC-SHA256\n" + amzDate + "\n" + scope + "\n" + canonicalRequestHash
|
|
|
|
signingKey := getSigningKey(defaultSecretAccessKey, dateStamp, defaultRegion, "s3")
|
|
seedSignature := getSignature(signingKey, stringToSign)
|
|
|
|
// Calculate chunk signatures
|
|
chunk1Hash := getSHA256Hash([]byte(chunk1Data))
|
|
chunk1StringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" +
|
|
seedSignature + "\n" + emptySHA256 + "\n" + chunk1Hash
|
|
chunk1Signature := getSignature(signingKey, chunk1StringToSign)
|
|
|
|
chunk2Hash := getSHA256Hash([]byte(chunk2Data))
|
|
chunk2StringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" +
|
|
chunk1Signature + "\n" + emptySHA256 + "\n" + chunk2Hash
|
|
chunk2Signature := getSignature(signingKey, chunk2StringToSign)
|
|
|
|
finalStringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" +
|
|
chunk2Signature + "\n" + emptySHA256 + "\n" + emptySHA256
|
|
finalSignature := getSignature(signingKey, finalStringToSign)
|
|
|
|
// Build the chunked payload
|
|
payload := fmt.Sprintf("400;chunk-signature=%s\r\n%s\r\n", chunk1Signature, chunk1Data) +
|
|
fmt.Sprintf("200;chunk-signature=%s\r\n%s\r\n", chunk2Signature, chunk2Data) +
|
|
fmt.Sprintf("0;chunk-signature=%s\r\n\r\n", finalSignature)
|
|
|
|
// Create the request
|
|
req, err := http.NewRequest("PUT", "http://s3.amazonaws.com/test-bucket/test-object",
|
|
bytes.NewReader([]byte(payload)))
|
|
assert.NoError(t, err)
|
|
|
|
req.Header.Set("Host", "s3.amazonaws.com")
|
|
req.Header.Set("x-amz-date", amzDate)
|
|
req.Header.Set("x-amz-content-sha256", hashedPayload)
|
|
req.Header.Set("Content-Encoding", "aws-chunked")
|
|
req.Header.Set("x-amz-decoded-content-length", "1536")
|
|
|
|
authHeader := fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s",
|
|
defaultAccessKeyId, scope, signedHeaders, seedSignature)
|
|
req.Header.Set("Authorization", authHeader)
|
|
|
|
// Test the chunked reader
|
|
reader, errCode := iam.newChunkedReader(req)
|
|
assert.Equal(t, s3err.ErrNone, errCode)
|
|
assert.NotNil(t, reader)
|
|
|
|
// Read and verify the payload
|
|
data, err := io.ReadAll(reader)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, chunk1Data+chunk2Data, string(data))
|
|
}
|
|
|
|
// createTrailerStreamingRequest creates a streaming upload request with trailer for testing.
|
|
// If useValidTrailerSignature is true, uses a correctly calculated trailer signature;
|
|
// otherwise uses an intentionally wrong signature for negative testing.
|
|
func createTrailerStreamingRequest(t *testing.T, useValidTrailerSignature bool) (*http.Request, string) {
|
|
chunk1Data := "hello world\n"
|
|
chunk1DataLen := len(chunk1Data)
|
|
chunk1DataLenHex := fmt.Sprintf("%x", chunk1DataLen)
|
|
|
|
// Use current time for signatures
|
|
now := time.Now().UTC()
|
|
amzDate := now.Format(iso8601Format)
|
|
dateStamp := now.Format(yyyymmdd)
|
|
|
|
// Calculate seed signature
|
|
scope := dateStamp + "/" + defaultRegion + "/s3/aws4_request"
|
|
|
|
// Build canonical request for seed signature
|
|
hashedPayload := "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER"
|
|
canonicalHeaders := "content-encoding:aws-chunked\n" +
|
|
"host:s3.amazonaws.com\n" +
|
|
"x-amz-content-sha256:" + hashedPayload + "\n" +
|
|
"x-amz-date:" + amzDate + "\n" +
|
|
fmt.Sprintf("x-amz-decoded-content-length:%d\n", chunk1DataLen) +
|
|
"x-amz-trailer:x-amz-checksum-crc32\n"
|
|
signedHeaders := "content-encoding;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-trailer"
|
|
|
|
canonicalRequest := "PUT\n" +
|
|
"/test-bucket/test-object\n" +
|
|
"\n" +
|
|
canonicalHeaders + "\n" +
|
|
signedHeaders + "\n" +
|
|
hashedPayload
|
|
|
|
canonicalRequestHash := getSHA256Hash([]byte(canonicalRequest))
|
|
stringToSign := "AWS4-HMAC-SHA256\n" + amzDate + "\n" + scope + "\n" + canonicalRequestHash
|
|
|
|
signingKey := getSigningKey(defaultSecretAccessKey, dateStamp, defaultRegion, "s3")
|
|
seedSignature := getSignature(signingKey, stringToSign)
|
|
|
|
// Calculate chunk signatures
|
|
chunk1Hash := getSHA256Hash([]byte(chunk1Data))
|
|
chunk1StringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" +
|
|
seedSignature + "\n" + emptySHA256 + "\n" + chunk1Hash
|
|
chunk1Signature := getSignature(signingKey, chunk1StringToSign)
|
|
|
|
// Final chunk (0 bytes)
|
|
finalStringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" +
|
|
chunk1Signature + "\n" + emptySHA256 + "\n" + emptySHA256
|
|
finalSignature := getSignature(signingKey, finalStringToSign)
|
|
|
|
// Calculate CRC32 checksum for trailer
|
|
crcWriter := crc32.NewIEEE()
|
|
_, crcErr := crcWriter.Write([]byte(chunk1Data))
|
|
assert.NoError(t, crcErr)
|
|
checksum := crcWriter.Sum(nil)
|
|
base64EncodedChecksum := base64.StdEncoding.EncodeToString(checksum)
|
|
|
|
// The on-wire trailer format uses \r\n (HTTP/aws-chunked convention)
|
|
trailerOnWire := "x-amz-checksum-crc32:" + base64EncodedChecksum + "\r\n"
|
|
|
|
// Calculate or use wrong trailer signature
|
|
var trailerSignature string
|
|
if useValidTrailerSignature {
|
|
// The canonical trailer content uses \n for signing (per AWS SigV4 spec)
|
|
trailerCanonical := "x-amz-checksum-crc32:" + base64EncodedChecksum + "\n"
|
|
trailerHash := getSHA256Hash([]byte(trailerCanonical))
|
|
trailerStringToSign := "AWS4-HMAC-SHA256-TRAILER\n" + amzDate + "\n" + scope + "\n" +
|
|
finalSignature + "\n" + trailerHash
|
|
trailerSignature = getSignature(signingKey, trailerStringToSign)
|
|
} else {
|
|
// Intentionally wrong signature for negative testing
|
|
trailerSignature = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
}
|
|
|
|
// Build the chunked payload with trailer and trailer signature
|
|
payload := fmt.Sprintf("%s;chunk-signature=%s\r\n%s\r\n", chunk1DataLenHex, chunk1Signature, chunk1Data) +
|
|
fmt.Sprintf("0;chunk-signature=%s\r\n", finalSignature) +
|
|
trailerOnWire +
|
|
"x-amz-trailer-signature:" + trailerSignature + "\r\n" +
|
|
"\r\n"
|
|
|
|
// Create the request
|
|
req, err := http.NewRequest("PUT", "http://s3.amazonaws.com/test-bucket/test-object",
|
|
bytes.NewReader([]byte(payload)))
|
|
assert.NoError(t, err)
|
|
|
|
req.Header.Set("Host", "s3.amazonaws.com")
|
|
req.Header.Set("x-amz-date", amzDate)
|
|
req.Header.Set("x-amz-content-sha256", hashedPayload)
|
|
req.Header.Set("Content-Encoding", "aws-chunked")
|
|
req.Header.Set("x-amz-decoded-content-length", fmt.Sprintf("%d", chunk1DataLen))
|
|
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32")
|
|
|
|
authHeader := fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s",
|
|
defaultAccessKeyId, scope, signedHeaders, seedSignature)
|
|
req.Header.Set("Authorization", authHeader)
|
|
|
|
return req, chunk1Data
|
|
}
|
|
|
|
// TestSignedStreamingUploadWithTrailer tests streaming uploads with signed chunks and trailers
|
|
// This tests the STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER content-sha256 header value
|
|
// which is used by AWS SDK v2 when checksum validation is enabled
|
|
func TestSignedStreamingUploadWithTrailer(t *testing.T) {
|
|
iam := setupIam()
|
|
req, expectedData := createTrailerStreamingRequest(t, true)
|
|
|
|
// Test the chunked reader
|
|
reader, errCode := iam.newChunkedReader(req)
|
|
assert.Equal(t, s3err.ErrNone, errCode)
|
|
assert.NotNil(t, reader)
|
|
|
|
// Read and verify the payload
|
|
data, err := io.ReadAll(reader)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedData, string(data))
|
|
}
|
|
|
|
// TestSignedStreamingUploadWithTrailerInvalidSignature tests behavior with invalid trailer signatures.
|
|
// This is a negative test case for trailer signature validation. It currently verifies that an invalid
|
|
// signature doesn't break content reading, and is prepared for when validation is implemented.
|
|
func TestSignedStreamingUploadWithTrailerInvalidSignature(t *testing.T) {
|
|
iam := setupIam()
|
|
req, expectedData := createTrailerStreamingRequest(t, false)
|
|
|
|
// Test the chunked reader - it should be created successfully
|
|
reader, errCode := iam.newChunkedReader(req)
|
|
assert.Equal(t, s3err.ErrNone, errCode)
|
|
assert.NotNil(t, reader)
|
|
|
|
// Read the payload - currently trailer signature validation may not be implemented,
|
|
// but this test documents the expected behavior and will catch regressions
|
|
// if trailer signature validation is added in the future
|
|
data, err := io.ReadAll(reader)
|
|
// Note: If trailer signature validation is implemented, this should fail with an error
|
|
// For now, we just verify the content is correctly extracted
|
|
if err != nil {
|
|
assert.Contains(t, err.Error(), "signature", "Error should indicate signature mismatch")
|
|
} else {
|
|
// If no error, content should still be correct (trailer sig validation not yet implemented)
|
|
assert.Equal(t, expectedData, string(data))
|
|
}
|
|
}
|
|
|
|
// TestSignedStreamingUploadInvalidSignature tests that invalid chunk signatures are rejected
|
|
// This is a negative test case to ensure signature validation is actually working
|
|
func TestSignedStreamingUploadInvalidSignature(t *testing.T) {
|
|
iam := setupIam()
|
|
|
|
// Create a simple streaming upload with 1 chunk
|
|
chunk1Data := strings.Repeat("a", 1024)
|
|
|
|
// Use current time for signatures
|
|
now := time.Now().UTC()
|
|
amzDate := now.Format(iso8601Format)
|
|
dateStamp := now.Format(yyyymmdd)
|
|
|
|
// Calculate seed signature
|
|
scope := dateStamp + "/" + defaultRegion + "/s3/aws4_request"
|
|
|
|
// Build canonical request for seed signature
|
|
hashedPayload := "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
|
|
canonicalHeaders := "content-encoding:aws-chunked\n" +
|
|
"host:s3.amazonaws.com\n" +
|
|
"x-amz-content-sha256:" + hashedPayload + "\n" +
|
|
"x-amz-date:" + amzDate + "\n" +
|
|
"x-amz-decoded-content-length:1024\n"
|
|
signedHeaders := "content-encoding;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length"
|
|
|
|
canonicalRequest := "PUT\n" +
|
|
"/test-bucket/test-object\n" +
|
|
"\n" +
|
|
canonicalHeaders + "\n" +
|
|
signedHeaders + "\n" +
|
|
hashedPayload
|
|
|
|
canonicalRequestHash := getSHA256Hash([]byte(canonicalRequest))
|
|
stringToSign := "AWS4-HMAC-SHA256\n" + amzDate + "\n" + scope + "\n" + canonicalRequestHash
|
|
|
|
signingKey := getSigningKey(defaultSecretAccessKey, dateStamp, defaultRegion, "s3")
|
|
seedSignature := getSignature(signingKey, stringToSign)
|
|
|
|
// Calculate chunk signature (correct)
|
|
chunk1Hash := getSHA256Hash([]byte(chunk1Data))
|
|
chunk1StringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" +
|
|
seedSignature + "\n" + emptySHA256 + "\n" + chunk1Hash
|
|
chunk1Signature := getSignature(signingKey, chunk1StringToSign)
|
|
|
|
// Calculate final signature (correct)
|
|
finalStringToSign := "AWS4-HMAC-SHA256-PAYLOAD\n" + amzDate + "\n" + scope + "\n" +
|
|
chunk1Signature + "\n" + emptySHA256 + "\n" + emptySHA256
|
|
finalSignature := getSignature(signingKey, finalStringToSign)
|
|
|
|
// Build the chunked payload with INTENTIONALLY WRONG chunk signature
|
|
// We'll use a modified signature to simulate a tampered request
|
|
wrongChunkSignatureBytes := []byte(chunk1Signature)
|
|
if len(wrongChunkSignatureBytes) > 0 {
|
|
// Flip the first hex character to guarantee a different signature
|
|
if wrongChunkSignatureBytes[0] == '0' {
|
|
wrongChunkSignatureBytes[0] = '1'
|
|
} else {
|
|
wrongChunkSignatureBytes[0] = '0'
|
|
}
|
|
}
|
|
wrongChunkSignature := string(wrongChunkSignatureBytes)
|
|
payload := fmt.Sprintf("400;chunk-signature=%s\r\n%s\r\n", wrongChunkSignature, chunk1Data) +
|
|
fmt.Sprintf("0;chunk-signature=%s\r\n\r\n", finalSignature)
|
|
|
|
// Create the request
|
|
req, err := http.NewRequest("PUT", "http://s3.amazonaws.com/test-bucket/test-object",
|
|
bytes.NewReader([]byte(payload)))
|
|
assert.NoError(t, err)
|
|
|
|
req.Header.Set("Host", "s3.amazonaws.com")
|
|
req.Header.Set("x-amz-date", amzDate)
|
|
req.Header.Set("x-amz-content-sha256", hashedPayload)
|
|
req.Header.Set("Content-Encoding", "aws-chunked")
|
|
req.Header.Set("x-amz-decoded-content-length", "1024")
|
|
|
|
authHeader := fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s",
|
|
defaultAccessKeyId, scope, signedHeaders, seedSignature)
|
|
req.Header.Set("Authorization", authHeader)
|
|
|
|
// Test the chunked reader - it should be created successfully
|
|
reader, errCode := iam.newChunkedReader(req)
|
|
assert.Equal(t, s3err.ErrNone, errCode)
|
|
assert.NotNil(t, reader)
|
|
|
|
// Try to read the payload - this should fail with signature validation error
|
|
_, err = io.ReadAll(reader)
|
|
assert.Error(t, err, "Expected error when reading chunk with invalid signature")
|
|
assert.Contains(t, err.Error(), "chunk signature does not match", "Error should indicate chunk signature mismatch")
|
|
}
|