CosmoS3 2.3.0
See the version list below for details.
dotnet add package CosmoS3 --version 2.3.0
NuGet\Install-Package CosmoS3 -Version 2.3.0
<PackageReference Include="CosmoS3" Version="2.3.0" />
<PackageVersion Include="CosmoS3" Version="2.3.0" />
<PackageReference Include="CosmoS3" />
paket add CosmoS3 --version 2.3.0
#r "nuget: CosmoS3, 2.3.0"
#:package CosmoS3@2.3.0
#addin nuget:?package=CosmoS3&version=2.3.0
#tool nuget:?package=CosmoS3&version=2.3.0
CosmoS3
CosmoS3 is an Amazon S3–compatible object-storage middleware library for CosmoApiServer. It implements core S3 operations with a pluggable SQL metadata store — SQL Server, PostgreSQL, MySQL, SQLite, or the embedded CosmoKv engine — and the local disk (or a pluggable storage driver) for object data.
It can be consumed three ways: as a standalone S3 endpoint (CosmoS3.Host), as middleware inside an existing CosmoApiServer app, or embedded in-process via the IS3Repository data layer.
Table of Contents
- Architecture
- Security & Correctness
- Performance
- Recent Updates
- Quick Start
- Configuration
- Database Schema
- S3 Feature Compatibility
- Static Website Hosting
- Presigned URLs
- Multipart Upload
- Using with AWS CLI
- Testing
- Project Structure
Architecture
┌─────────────────────────────────────────────────────┐
│ CosmoApiServer │
│ (System.IO.Pipelines transport, HTTP/1.1·2·3) │
└──────────────────────┬───────────────────────────────┘
│ IMiddleware
▼
┌──────────────────────────────────────────────────────┐
│ S3Middleware │
│ • S3Context.CreateAsync — async, non-blocking parse │
│ • Authenticates (SigV4 / SigV2 / presigned) │
│ • Verifies the SigV4 signature (ValidateSignatures) │
│ • Routes to ServiceHandler / BucketHandler / │
│ ObjectHandler / Admin + internal handlers │
└────────────────┬───────────────────────┬──────────────┘
│ │
┌───────────▼───────┐ ┌───────────▼────────────┐
│ DataAccess │ │ Storage Driver │
│ → IS3Repository │ │ (DiskStorageDriver: │
│ (CosmoSQLClient, │ │ atomic write + move, │
│ 5 SQL backends) │ │ ArrayPool, SubStream) │
└────────────────────┘ └─────────────────────────┘
Key types:
| Type | Role |
|---|---|
S3Middleware |
Entry point; implements IMiddleware. Builds the request via S3Context.CreateAsync (fully async — no sync-over-async body buffering) and enforces SigV4 when ValidateSignatures is on |
S3Request / S3Response / S3Context |
Async request parse, S3-formatted response writer, combined handler context |
AuthManager |
Resolves the credential/user and computes the authorization decision (SigV2 / SigV4 / presigned) |
SignatureV4Verifier / Helpers/SignatureV4 |
Recomputes the AWS SigV4 canonical request and compares signatures in constant time |
BucketManager |
Lock-free in-memory bucket registry (ConcurrentDictionary); lazily populated from the DB on a miss |
ConfigManager |
User / credential / bucket / object lookup (DB or seeded no-DB mode) |
DataAccess → IS3Repository |
Static facade over S3Repository — parameterized SQL via CosmoSQLClient, backend-portable across all five engines |
DiskStorageDriver |
Streaming I/O using ArrayPool<byte> and SubStream; writes to a temp sibling then File.Move for atomic, durable publish |
Security & Correctness
The design choices below are deliberate; each closes a concrete failure mode. They are enforced by the unit + integration test suite (see Testing).
| Area | Behavior | Why |
|---|---|---|
| SigV4 verification | When ValidateSignatures is true (default) the server recomputes the canonical request and compares signatures with CryptographicOperations.FixedTimeEquals, plus a ±15-minute clock-skew check (RequestTimeTooSkewed). Validated against AWS's official get-vanilla test vector. |
Authentication that parses a signature but never checks it is no authentication. Constant-time comparison avoids leaking the secret via timing. |
| No default admin key | AdminApiKey defaults to ""; an empty configured key never matches any presented key (SecurityHelper.ConfiguredKeyMatches). |
A shipped default master key (the old "cosmos3admin") is a backdoor. Operators must set one explicitly. |
| Real principal on writes | Object writes are credited to the authenticated user. Anonymous / public-write requests fall back to the configurable AnonymousOwnerGuid; if that is unset the write is rejected with AccessDenied. |
The former 0.0.0.0:0 sentinel owner resolved to no user, which made GET ?acl return 500. Owners must reference a real principal. |
| Bucket-name validation | SecurityHelper.IsValidBucketName rejects path-traversal and out-of-spec names before they reach the filesystem. |
Bucket names become directory names; ../ must never escape the storage root. |
| Atomic, durable objects | The disk driver writes to a unique temp sibling, flush(true), then File.Move to publish; a short read against the declared length throws instead of publishing a truncated object. |
A crash mid-write must never leave a half-written object visible. |
| Versioning write race | (bucketguid, objectkey, version) is a UNIQUE index; concurrent writers that collide on a version retry with the next number. |
Two simultaneous PUTs to the same key must not silently produce duplicate or lost versions. |
| Conditional requests | If-Match / If-None-Match / If-[Un]Modified-Since are honored on GET/HEAD (304 / 412) and PUT (If-None-Match: * overwrite guard, If-Match optimistic concurrency) per RFC 7232. |
Lets clients do safe caching and lost-update-free updates. |
| Bounded-memory listing | ListObjects(V2) streams rows and stops at the page boundary, then hydrates only that page's keys — it never loads a whole bucket into memory. |
A bucket with millions of keys must not OOM the server on a single ls. |
| Single-pass multipart | CompleteMultipartUpload streams the part files straight into the destination blob (ConcatReadStream) instead of concatenating to a temp file and re-copying. |
Avoids writing every byte twice and the extra temp-space high-water mark on large objects. |
Performance
CosmoS3 is optimized for high-throughput and low-latency workloads. Recent architectural improvements have pushed performance to near-NVMe speeds on local hardware.
Benchmark: CosmoS3 vs. MinIO (Throughput in MiB/s)
Tests were performed locally using the warp S3 benchmark tool with 1MiB and 16MiB object sizes.
| Operation | Size | Concurrency | CosmoS3 | MinIO | CosmoS3 vs MinIO |
|---|---|---|---|---|---|
| PUT | 1MiB | 32 | 402.67 | 370.33 | +8.7% |
| PUT | 16MiB | 32 | 654.66 | 471.79 | +38.7% |
| GET | 1KB | 1 | 7.5 | 19.3 | +157.3% |
| GET | 1MiB | 8 | 951.58 | 513.32 | +85.3% |
| GET | 16MiB | 8 | 936.21 | 690.99 | +35.4% |
| GET | 16MiB | 32 | 860.05 | 603.13 | +42.5% |
Key Optimizations:
- Zero-Allocation I/O: Integrated
ArrayPool<byte>across all read/write paths to virtually eliminate transient allocations and GC pressure. - End-to-End Streaming: Implemented
SubStreamfor range requests, allowing data to be streamed directly from disk to the transport without intermediate buffering. - Lock-Free Concurrency: Migrated bucket registry to
ConcurrentDictionary, enabling non-blocking lookups during high-concurrency requests. - Metadata Acceleration: composite UNIQUE index on
(bucketguid, objectkey, version DESC)for near-instant latest-version lookups, plus a lock-free in-memory bucket registry so the hot HEAD/GET/PUT path skips the DB on a cache hit.
Recent Updates
- SigV4 signature verification: requests are now cryptographically verified (constant-time, ±15-min skew), validated against the official AWS test vector — not just parsed.
- Conditional requests (RFC 7232):
If-Match/If-None-Match/If-[Un]Modified-Sinceon GET/HEAD/PUT (304 / 412, overwrite guard, optimistic concurrency). - Object versioning: per-key version rows with a UNIQUE
(bucket, key, version)index and collision-retry assignment;GET/PUT ?versioning,ListObjectVersions, andx-amz-version-id. - Bounded-memory listing:
ListObjects(V2)streams and pages at the database level instead of loading the whole bucket. - Single-pass multipart assembly: parts stream directly into the destination blob — no temp-file double-write.
- Hardened defaults: no default admin key, anonymous writes off unless an owner is configured, path-traversal-safe bucket names.
- Five SQL backends: SQL Server, PostgreSQL, MySQL, SQLite, and the embedded CosmoKv engine, behind one backend-portable
IS3Repository. - Fully async request path:
S3Context.CreateAsyncremoved the sync-over-async body buffering (and the thread-pool workaround it required). - Static website hosting,
aws-chunkedstreaming decode, and a benchmarking suite for comparison against any S3-compatible backend.
Quick Start
1. Run a sample host
A ready-to-run host is included at samples/CosmoS3Host/, with backend-specific variants and demos alongside it:
cd samples/CosmoS3Host # default
dotnet run
| Sample | Purpose |
|---|---|
CosmoS3Host |
Default standalone S3 endpoint |
CosmoS3Host.SqlServer / .Postgres / .MySQL / .SQLite |
Same host wired to each SQL backend |
InternalApiDemo |
Using the lightweight internal REST API (InternalApiKey) |
CosmoBroker.AuthDemo |
Authentication / credential wiring |
2. Wire CosmoS3 into your own CosmoApiServer app
using CosmoS3;
using CosmoS3.Settings;
var settings = new SettingsBase
{
RegionString = "us-east-1",
ValidateSignatures = false, // set true in production
Storage = new StorageSettings
{
StorageType = CosmoS3.Storage.StorageDriverType.Disk,
DiskDirectory = "./data/objects"
},
Database = new DatabaseSettings
{
Hostname = "localhost",
Port = 1433,
DatabaseName = "MyDatabase",
Username = "sa",
Password = "your-password"
},
// Optional: enable CORS for browser-based S3 clients
Cors = new CorsSettings { Enabled = true },
// Optional: enable HTTPS
// CertificatePath = "./certs/server.pfx",
// CertificatePassword = "changeme",
// Optional: enable HTTP/2 cleartext (h2c)
// EnableHttp2 = true,
};
// CosmoS3Application.Create() wires TLS, HTTP/2, CORS, logging, and S3Middleware.
var app = CosmoS3Application.Create(settings, port: 8100);
app.Run();
Or wire manually for full control over the middleware order:
using CosmoApiServer.Core.Hosting;
using CosmoS3;
using CosmoS3.Settings;
var app = CosmoWebApplicationBuilder.Create()
.ListenOn(8100)
.UseHttps("./certs/server.pfx", "changeme") // optional TLS
.UseHttp3() // optional HTTP/3 over QUIC
.UseHttp2() // optional h2c
.UseCors() // optional CORS
.UseLogging()
.UseMiddleware(new S3Middleware(settings))
.Build();
app.Run();
Configuration
SettingsBase
| Property | Type | Default | Description |
|---|---|---|---|
ValidateSignatures |
bool |
true |
Verify AWS SigV4/V2 on every request. When on, signatures are recomputed and checked (not just parsed). Disable for local dev only. |
BaseDomain |
string? |
null |
Set to enable virtual-hosted–style URLs (e.g. "localhost"). Leave null for path-style. |
RegionString |
string |
"us-west-1" |
AWS region identifier returned in responses. |
HeaderApiKey |
string |
"x-api-key" |
HTTP header name for admin API authentication. |
AdminApiKey |
string |
"" |
Secret value expected in HeaderApiKey for admin endpoints. Empty by default — an empty key never matches; set one to enable the admin API. |
InternalApiKey |
string? |
null |
When set, enables the lightweight internal REST API at /internal/ for requests carrying Authorization: Bearer {InternalApiKey}. Null disables it. |
AnonymousOwnerGuid |
string |
"" |
Principal credited as owner when an anonymous / public-write request creates an object. Empty rejects anonymous writes (AccessDenied); set it to a real user GUID to allow them. |
Database |
DatabaseSettings |
(required) | Metadata-store connection (any of the five backends). |
Storage |
StorageSettings |
(required) | Object storage configuration. |
Logging |
LoggingSettings |
default | Log level callbacks. |
Debug |
DebugSettings |
default | Enable extra debug output. |
Users / Credentials / Buckets |
List<T> |
empty | Seed in-memory data for no-database mode (testing). |
CertificatePath |
string? |
null |
Path to PFX file for HTTPS. When set, TLS is automatically applied. |
CertificatePassword |
string? |
null |
Password for the PFX certificate. |
EnableHttp2 |
bool |
false |
Enable h2c (HTTP/2 cleartext) support. |
EnableHttp3 |
bool |
false |
Enable HTTP/3 over QUIC on the TLS listener. Requires CertificatePath. |
Cors |
CorsSettings |
disabled | CORS configuration for browser-based S3 clients. |
DatabaseSettings
CosmoS3 selects the backend from DatabaseType; the schema is created/updated at startup via DatabaseFactory.EnsureSchemaAsync.
| Property | Default | Description |
|---|---|---|
DatabaseType |
"mssql" |
One of mssql, postgres, mysql, sqlite, cosmokv, kvdirect. |
ConnectionString |
null |
Full connection string. When set, used verbatim (required for sqlite/cosmokv/kvdirect, e.g. Data Source=...). |
Hostname / Port / Username / Password / DatabaseName / Instance |
— | Convenience fields for the server-based engines; used to build a connection string when ConnectionString is not given. |
For SQL Server the built connection string is:
server=HOSTNAME,PORT;database=DBNAME;user id=USER;password=PASS;TrustServerCertificate=true;
SQLite and the embedded CosmoKv engine need no server — point ConnectionString at a file/data-source path.
kvdirect — embedded ordered-KV metadata (no SQL)
kvdirect stores all metadata directly on the CosmoKv ordered-KV engine — the same LSM store the
cosmokv backend uses, but without the SQL parser/planner/connection-pool on top. Object keys are
byte-ordered (<bucket> 0x00 <key> 0x00 <invVersion>) so the latest version is a single seek and a
bucket listing is a native cursor; there is no schema (the default user/credential/bucket/owner-ACL are
seeded automatically on first open). Point ConnectionString at a directory:
"Database": { "DatabaseType": "kvdirect", "ConnectionString": "Data Source=./cosmos3-kvdirect" }
It is single-process / single-node by design (one embedded store, no shared DB) — the MinIO-style
"just run the binary" deployment. Use a SQL backend (postgres/mssql/mysql) when multiple nodes must
share one metadata store. End-to-end it runs ~6.3× faster across PUT/GET/HEAD/DELETE than the same
engine via SQL; see benchmarks/results/kvdirect-spike-SUMMARY.md.
StorageSettings
| Property | Default | Description |
|---|---|---|
StorageType |
Disk |
Disk is the only currently supported driver |
DiskDirectory |
"./disk/" |
Root directory for object files (no trailing slash) |
TempDirectory |
"./temp/" |
Scratch directory for multipart upload assembly |
Database Schema
CosmoS3 supports multiple database engines including SQL Server, PostgreSQL, MySQL, and SQLite. The schema is automatically created or updated at startup via DatabaseFactory.EnsureSchemaAsync.
Key Tables
| Table | Purpose |
|---|---|
s3_users |
S3 user accounts |
s3_credentials |
Access key / secret key pairs linked to users |
s3_buckets |
Bucket metadata (name, owner, region, storage config) |
s3_objects |
Object metadata (key, size, ETag, version, blob reference) |
s3_objecttags |
Per-object tags |
s3_buckettags |
Per-bucket tags |
s3_uploads |
Active multipart upload sessions |
s3_uploadparts |
Uploaded parts for active sessions |
Key Indexes for Performance & Correctness
The schema includes composite indexes that stay fast with millions of objects and also enforce integrity:
ux_s3_objects_bucket_key_version: UNIQUE(bucketguid, objectkey, version DESC)— serves the common "get latest version" lookup and makes the versioning write race impossible at the storage layer (collisions retry with the next version). On CosmoKv theDESCqualifier is dropped (engine limitation) but the uniqueness constraint is identical.idx_s3_objects_guid:(guid)— blob/version resolution by object GUID.idx_s3_credentials_accesskey:(accesskey)— rapid authentication.idx_s3_buckets_name:(name)— fast bucket resolution.
All database operations go through the static DataAccess facade over the backend-portable IS3Repository (S3Repository), using parameterized SQL — no stored procedures, so the same code runs unchanged on every backend.
S3 Feature Compatibility
Service-Level Operations
| Operation | AWS CLI command | Status |
|---|---|---|
| List Buckets | aws s3 ls |
✅ |
Bucket Operations
| Operation | AWS CLI command | Status |
|---|---|---|
| Create Bucket | aws s3 mb s3://bucket |
✅ |
| Delete Bucket | aws s3 rb s3://bucket |
✅ |
| List Objects (v1 & v2) | aws s3 ls s3://bucket/ |
✅ |
| Get Bucket ACL | aws s3api get-bucket-acl |
✅ |
| Put Bucket ACL | aws s3api put-bucket-acl |
✅ |
| Get Bucket Tags | aws s3api get-bucket-tagging |
✅ |
| Put Bucket Tags | aws s3api put-bucket-tagging |
✅ |
| Delete Bucket Tags | aws s3api delete-bucket-tagging |
✅ |
| Get/Put/Delete Bucket Website | aws s3api *-bucket-website |
✅ |
| Get Bucket Location | aws s3api get-bucket-location |
✅ |
| Get Bucket Versioning | aws s3api get-bucket-versioning |
✅ |
Object Operations
| Operation | AWS CLI command | Status |
|---|---|---|
| Put Object | aws s3 cp local.txt s3://bucket/key |
✅ |
| Get Object | aws s3 cp s3://bucket/key local.txt |
✅ |
| Head Object | aws s3api head-object |
✅ |
| Delete Object | aws s3 rm s3://bucket/key |
✅ |
| Delete Objects (batch) | aws s3 sync --delete |
✅ |
| Copy Object | aws s3 cp s3://src s3://dst |
✅ |
| Get Object ACL | aws s3api get-object-acl |
✅ |
| Put Object ACL | aws s3api put-object-acl |
✅ |
| Get Object Tags | aws s3api get-object-tagging |
✅ |
| Put Object Tags | aws s3api put-object-tagging |
✅ |
| Delete Object Tags | aws s3api delete-object-tagging |
✅ |
| Presigned GET/PUT URLs | SDK GetPreSignedURL |
✅ |
| Multipart Upload | aws s3 cp (large files) |
✅ |
| List Multipart Uploads | aws s3api list-multipart-uploads |
✅ |
| Abort Multipart Upload | aws s3api abort-multipart-upload |
✅ |
| Conditional GET/HEAD/PUT | If-Match / If-None-Match / If-[Un]Modified-Since |
✅ |
| List Object Versions | aws s3api list-object-versions |
✅ |
Notes on Compatibility
- Signature versions: Both SigV4 and SigV2 are supported for authentication and presigned URLs. With
ValidateSignaturesenabled (default), SigV4 signatures are cryptographically verified, not merely parsed. - Conditional requests: GET/HEAD return
304 Not Modified/412 Precondition Failed; PUT supportsIf-None-Match: *(don't overwrite) andIf-Match(optimistic concurrency) per RFC 7232. aws-chunkedtransfer encoding: Automatically decoded via the async body path; works withaws s3 cpfor any file size.- Versioning: Supported. Toggle with
PUT ?versioning; when enabled, writes create new version rows, deletes write delete markers, and responses carryx-amz-version-id. List historic versions withlist-object-versions. - Anonymous writes: Off by default — set
AnonymousOwnerGuidto a real user GUID to allow public-write buckets. - Bucket policies / lifecycle / replication: Not implemented. (CORS is configured server-side via
CorsSettings, not per-bucket.)
Static Website Hosting
A bucket can be configured to serve static files over plain HTTP (no AWS credentials required).
Configure a bucket for website hosting
# Create bucket
aws --endpoint-url http://localhost:8100 s3 mb s3://my-site
# Upload content
aws --endpoint-url http://localhost:8100 s3 cp index.html s3://my-site/index.html --content-type text/html
aws --endpoint-url http://localhost:8100 s3 cp error.html s3://my-site/error.html --content-type text/html
# Enable website hosting
aws --endpoint-url http://localhost:8100 s3 website s3://my-site \
--index-document index.html \
--error-document error.html
Browse the site
# Bucket root returns index.html
curl http://localhost:8100/my-site/
# Unknown path returns error.html with 404
curl http://localhost:8100/my-site/missing.html
Redirect all requests
aws --endpoint-url http://localhost:8100 s3api put-bucket-website \
--bucket my-site \
--website-configuration '{
"RedirectAllRequestsTo": { "HostName": "example.com", "Protocol": "https" }
}'
Routing rules
aws --endpoint-url http://localhost:8100 s3api put-bucket-website \
--bucket my-site \
--website-configuration '{
"IndexDocument": { "Suffix": "index.html" },
"ErrorDocument": { "Key": "error.html" },
"RoutingRules": [
{
"Condition": { "KeyPrefixEquals": "old/" },
"Redirect": { "ReplaceKeyPrefixWith": "new/" }
}
]
}'
How it works:
- Website configuration is stored as
website.xmlat<DiskDirectory>/<bucketName>/website.xml. - Requests to a website-enabled bucket without AWS authentication headers are served as static files.
- If the request path ends with
/, the index document is served. - If the object is not found, the error document is returned with HTTP 404.
- Redirect rules are evaluated before object lookup.
Presigned URLs
Presigned URLs grant time-limited access to an S3 object without requiring the caller to have AWS credentials.
Generate a presigned URL (C# SDK)
var request = new GetPreSignedUrlRequest
{
BucketName = "my-bucket",
Key = "my-object.txt",
Expires = DateTime.UtcNow.AddMinutes(15),
Verb = HttpVerb.GET
};
string url = s3Client.GetPreSignedURL(request);
Use the presigned URL
# Download with curl (no AWS credentials needed)
curl "<presigned-url>" -o downloaded.txt
# Upload with a presigned PUT URL
curl -X PUT "<presigned-put-url>" --data-binary @file.txt
Signature version behavior:
AWSSDK generates SigV2 presigned URLs for custom (non-AWS) endpoints. CosmoS3 validates both:
| Version | Query params |
|---|---|
| SigV2 | AWSAccessKeyId, Signature, Expires (Unix timestamp) |
| SigV4 | X-Amz-Credential, X-Amz-Signature, X-Amz-Expires |
Expired presigned URLs return HTTP 403 ExpiredToken.
Multipart Upload
Multipart upload allows large files to be uploaded in parts and assembled server-side.
Via AWS CLI (automatic for files > 8 MB by default)
# CosmoS3 handles chunked uploads transparently
aws --endpoint-url http://localhost:8100 \
s3 cp large-file.bin s3://my-bucket/large-file.bin \
--expected-size 1073741824 # hint for 1 GB file
Via SDK (manual)
// 1. Initiate upload
var initResponse = await s3.InitiateMultipartUploadAsync(new InitiateMultipartUploadRequest
{
BucketName = "my-bucket",
Key = "my-object"
});
string uploadId = initResponse.UploadId;
// 2. Upload parts (minimum 5 MB each, except the last)
var uploadPartResponse = await s3.UploadPartAsync(new UploadPartRequest
{
BucketName = "my-bucket",
Key = "my-object",
UploadId = uploadId,
PartNumber = 1,
InputStream = partStream,
PartSize = partStream.Length
});
// 3. Complete the upload
await s3.CompleteMultipartUploadAsync(new CompleteMultipartUploadRequest
{
BucketName = "my-bucket",
Key = "my-object",
UploadId = uploadId,
PartETags = new List<PartETag> { new PartETag(1, uploadPartResponse.ETag) }
});
Internals:
- Each uploaded part is written to a part file under
TempDirectoryand hashed on arrival. CompleteMultipartUploadstreams the ordered part files directly into the destination blob viaConcatReadStream(single pass — no concatenate-to-temp-then-recopy), then deletes the parts. The object length is the sum of the recorded part lengths.- Active sessions and their parts are tracked in
s3_uploads/s3_uploadpartsand can be listed or aborted.
Using with AWS CLI
Configure the AWS CLI for local use
aws configure
# AWS Access Key ID: default
# AWS Secret Access Key: default
# Default region name: us-east-1
# Default output format: json
Common commands
ENDPOINT=http://localhost:8100
# List buckets
aws --endpoint-url $ENDPOINT s3 ls
# Create bucket
aws --endpoint-url $ENDPOINT s3 mb s3://my-bucket
# Upload file
aws --endpoint-url $ENDPOINT s3 cp file.txt s3://my-bucket/
# Download file
aws --endpoint-url $ENDPOINT s3 cp s3://my-bucket/file.txt ./
# List objects
aws --endpoint-url $ENDPOINT s3 ls s3://my-bucket/
# Sync directory
aws --endpoint-url $ENDPOINT s3 sync ./local-dir/ s3://my-bucket/prefix/
# Delete object
aws --endpoint-url $ENDPOINT s3 rm s3://my-bucket/file.txt
# Delete bucket (must be empty)
aws --endpoint-url $ENDPOINT s3 rb s3://my-bucket
# Tag a bucket
aws --endpoint-url $ENDPOINT s3api put-bucket-tagging \
--bucket my-bucket \
--tagging '{"TagSet":[{"Key":"env","Value":"dev"}]}'
# Get bucket tags
aws --endpoint-url $ENDPOINT s3api get-bucket-tagging --bucket my-bucket
# Get bucket website config
aws --endpoint-url $ENDPOINT s3api get-bucket-website --bucket my-bucket
# Presigned URL (60 seconds)
aws --endpoint-url $ENDPOINT s3 presign s3://my-bucket/file.txt --expires-in 60
Testing
The suite (tests/CosmoS3.Tests/) uses xUnit + AWSSDK.S3 and needs no external database or running server — it boots the real server in-process against an embedded CosmoKv DB.
Tests are split into two xUnit traits that must run as separate dotnet test invocations, because the server's data layer is a process-global singleton (DataAccess._repo), so only one server instance can exist per process:
# Server-free unit tests (signature math, helpers, conditional-header logic, ...)
dotnet test tests/CosmoS3.Tests --filter "Category=Unit"
# Integration tests — boot the in-process server, drive it through the AWS SDK
dotnet test tests/CosmoS3.Tests --filter "Category=Integration"
CI runs the two lanes as separate steps (.github/workflows/ci.yml). Current status: 62 unit + 12 integration, all passing.
Fixtures
CosmoServerFixture(integration) boots the server withCosmoWebApplication.RunAsyncon an ephemeral port over a temp CosmoKv database, and hands tests a configuredAmazonS3Client(NewS3Client()). Shared via the[Collection("CosmoServer")]xUnit collection.S3Fixtureis the legacy fixture that targets an externally running endpoint, retained for the original end-to-end tests.
Representative coverage
| Area | Tests |
|---|---|
SigV4 signing math (official AWS get-vanilla vector) + live accept/reject |
SignatureV4Tests, SignatureV4IntegrationTests |
| Conditional headers (304 / 412, RFC 7232 precedence) | ConditionalHeadersTests, ConditionalHeadersIntegrationTests |
| Listing: pagination, delimiter common-prefixes, delete-marker hiding | ListingPagingIntegrationTests |
| Multipart ordered round-trip | MultipartIntegrationTests |
| ACL owner resolution after authenticated PUT (M2) | OwnerAclIntegrationTests |
| Security helpers, data integrity, streaming safety, bucket cache | SecurityHelperTests, DataIntegrityTests, StreamingSafetyTests, BucketCacheTests |
| Bucket / object / multipart / presigned / website end-to-end | BucketTests, ObjectTests, MultipartTests, PresignedUrlTests, WebsiteTests |
Project Structure
src/CosmoS3/
├── S3Middleware.cs # IMiddleware entry point; async parse, SigV4 verify, routing
├── S3Request.cs # HTTP → S3 request parsing (CreateAsync, aws-chunked decode)
├── S3Response.cs # S3-formatted response writer
├── S3Context.cs # Combined request + response context (CreateAsync factory)
├── S3Exception.cs # Typed S3 error thrown by handlers
├── SignatureV4Verifier.cs # Recompute + constant-time compare of the SigV4 signature
├── DataAccess.cs # Static facade over IS3Repository
├── IS3Repository.cs # Backend-portable data-layer contract
├── S3Repository.cs # Parameterized-SQL implementation (all 5 SQL backends)
├── KvDirectRepository.cs # Embedded ordered-KV implementation (no SQL — DatabaseType=kvdirect)
├── DatabaseFactory.cs # Backend selection + schema bootstrap
├── CosmoS3Application.cs # Turn-key host builder (TLS / HTTP2·3 / CORS / middleware)
├── Settings/ # SettingsBase, Database/Storage/Logging/Debug/Cors settings
├── Helpers/
│ ├── SignatureV4.cs # SigV4 canonical-request + signing-key primitives
│ ├── SecurityHelper.cs # FixedTimeEquals, ConfiguredKeyMatches, IsValidBucketName
│ ├── ConditionalHeaders.cs# RFC 7232 If-Match / If-None-Match / If-[Un]Modified-Since
│ ├── ConcatReadStream.cs # Single-pass multipart assembly stream
│ ├── AclConverter.cs # ACL ⇆ policy conversion (grantees validated vs DB)
│ ├── RequestValidator.cs # Shared handler precondition checks
│ └── HashHelper.cs # Single-pass MD5/SHA hashing
├── Classes/
│ ├── AuthManager.cs # Credential/user resolution + authorization decision
│ ├── BucketManager.cs # Lock-free ConcurrentDictionary bucket registry
│ ├── BucketClient.cs # Per-bucket storage driver accessor
│ ├── ConfigManager.cs # User / credential / bucket / object lookup
│ └── CleanupManager.cs # Background task: expire stale temp files
├── Api/S3/
│ ├── ApiHandler.cs # Top-level dispatcher (service/bucket/object)
│ ├── ServiceHandler.cs # ListBuckets
│ ├── BucketHandler.cs # Bucket operations (incl. versioning, website, ACL, tags)
│ ├── ObjectHandler.cs # Object operations + multipart + conditional PUT
│ └── ApiHelper.cs # Shared XML response helpers
├── Storage/
│ ├── StorageDriverBase.cs
│ └── DiskStorageDriver.cs # Atomic temp-then-move writes; ArrayPool + SubStream I/O
├── Schema/ # Per-backend DDL: mssql · postgres · mysql · sqlite · cosmokv
├── S3Objects/ # XML DTOs (request/response bodies)
└── Logging/
└── S3Logger.cs # Console/callback-based logger
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CosmoApiServer.Core (>= 3.8.0)
- CosmoNova (>= 1.1.11)
- CosmoSQLClient.Core (>= 6.2.8)
- CosmoSQLClient.CosmoKv (>= 6.2.7)
- CosmoSQLClient.MsSql (>= 6.2.8)
- CosmoSQLClient.MySql (>= 6.2.8)
- CosmoSQLClient.Postgres (>= 6.2.8)
- CosmoSQLClient.Sqlite (>= 6.2.8)
- Microsoft.Extensions.Caching.Memory (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.3.5 | 61 | 6/16/2026 |
| 2.3.4 | 63 | 6/16/2026 |
| 2.3.3 | 58 | 6/16/2026 |
| 2.3.2 | 143 | 6/16/2026 |
| 2.3.1 | 74 | 6/14/2026 |
| 2.3.0 | 80 | 6/14/2026 |
| 2.2.0 | 75 | 6/14/2026 |
| 2.1.0 | 69 | 6/14/2026 |
| 2.0.1 | 71 | 6/14/2026 |
| 2.0.0 | 68 | 6/13/2026 |
| 1.9.25 | 97 | 5/24/2026 |
| 1.9.24 | 96 | 5/24/2026 |
| 1.9.23 | 103 | 5/23/2026 |
| 1.9.22 | 102 | 5/18/2026 |
| 1.9.21 | 124 | 5/18/2026 |
| 1.9.20 | 96 | 5/10/2026 |
| 1.9.19 | 103 | 5/10/2026 |
| 1.9.18 | 106 | 5/10/2026 |
| 1.9.17 | 93 | 5/3/2026 |
| 1.9.16 | 106 | 4/26/2026 |