test(lightningstor): Add comprehensive S3 auth security tests
Added 9 security tests to verify SigV4 authentication hardening: - Invalid/malformed auth header rejection - Signature changes with different secret keys - Signature changes with different request components (body, URI, headers, query params) - Credential lookup for unknown keys - Empty credentials fallback - Malformed S3_CREDENTIALS handling Result: 19/19 auth tests passing (10 original + 9 new security tests) Task: T058.S3 Complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
07b3320436
commit
5c1cd9f9fc
2 changed files with 338 additions and 10 deletions
|
|
@ -11,7 +11,7 @@
|
||||||
## Deliverables (top-level)
|
## Deliverables (top-level)
|
||||||
> **Naming (2025-12-11):** Nightlight→NightLight, PrismNET→PrismNET, PlasmaCloud→PhotonCloud
|
> **Naming (2025-12-11):** Nightlight→NightLight, PrismNET→PrismNET, PlasmaCloud→PhotonCloud
|
||||||
- chainfire - cluster KVS lib - crates/chainfire-* - operational (DELETE fixed; 2/3 integration tests pass, 1 flaky)
|
- chainfire - cluster KVS lib - crates/chainfire-* - operational (DELETE fixed; 2/3 integration tests pass, 1 flaky)
|
||||||
- iam (aegis) - IAM platform - iam/crates/* - TESTS FAIL (private module visibility issue)
|
- iam (aegis) - IAM platform - iam/crates/* - operational (visibility fixed)
|
||||||
- flaredb - DBaaS KVS - flaredb/crates/* - operational
|
- flaredb - DBaaS KVS - flaredb/crates/* - operational
|
||||||
- plasmavmc - VM infra - plasmavmc/crates/* - operational (T054 Ops Planned)
|
- plasmavmc - VM infra - plasmavmc/crates/* - operational (T054 Ops Planned)
|
||||||
- lightningstor - object storage - lightningstor/crates/* - operational (T047 Complete, T058 Auth Planned)
|
- lightningstor - object storage - lightningstor/crates/* - operational (T047 Complete, T058 Auth Planned)
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
- **creditservice** - credit/quota management - creditservice/crates/* - operational (fixed - uses CAS instead of txn)
|
- **creditservice** - credit/quota management - creditservice/crates/* - operational (fixed - uses CAS instead of txn)
|
||||||
|
|
||||||
## MVP Milestones
|
## MVP Milestones
|
||||||
- **MVP-Alpha (PARTIAL)**: All 12 infrastructure components operational + specs | Status: 2/3 audit issues fixed | 2025-12-12 | creditservice✓ chainfire✓ iam pending (1-line fix)
|
- **MVP-Alpha (ACHIEVED)**: All 12 infrastructure components operational + specs | Status: T059 complete (creditservice✓ chainfire✓ iam✓) | 2025-12-12
|
||||||
- **MVP-Beta (ACHIEVED)**: E2E tenant path functional + FlareDB metadata unified | Gate: T023 complete ✓ | 2025-12-09
|
- **MVP-Beta (ACHIEVED)**: E2E tenant path functional + FlareDB metadata unified | Gate: T023 complete ✓ | 2025-12-09
|
||||||
- **MVP-K8s (ACHIEVED)**: K8s hosting with multi-tenant isolation | Gate: T025 S6.1 complete ✓ | 2025-12-09 | IAM auth + PrismNET CNI
|
- **MVP-K8s (ACHIEVED)**: K8s hosting with multi-tenant isolation | Gate: T025 S6.1 complete ✓ | 2025-12-09 | IAM auth + PrismNET CNI
|
||||||
- MVP-Production (future): HA, monitoring, production hardening | Gate: post-K8s
|
- MVP-Production (future): HA, monitoring, production hardening | Gate: post-K8s
|
||||||
|
|
@ -44,10 +44,10 @@
|
||||||
|
|
||||||
## Roadmap (Now/Next/Later)
|
## Roadmap (Now/Next/Later)
|
||||||
- **Now (<= 2 weeks):**
|
- **Now (<= 2 weeks):**
|
||||||
- **T059 ACTIVE (P0)**: Critical Audit Fix — creditservice compile, chainfire tests, iam tests (BLOCKS MVP-Alpha)
|
- **T058 ACTIVE (P0)**: LightningSTOR S3 Auth Hardening — S1 SigV4 ✓, S2 IAM ✓, S3 Security Tests (in progress)
|
||||||
- **T039 BLOCKED**: Production Deployment — Blocked by T059
|
- **T059 COMPLETE**: Critical Audit Fix — S1 creditservice ✓, S2 chainfire ✓, S3 iam ✓ (MVP-Alpha ACHIEVED)
|
||||||
- **T058 PLANNED**: LightningSTOR S3 Auth Hardening — Fix SigV4 Auth for Production (P0)
|
- **T039 ACTIVE**: Production Deployment — Unblocked by T059 completion; blocked by T058 completion
|
||||||
- **T052 BLOCKED**: CreditService Persistence — Blocked by T059.S1 (creditservice must compile first)
|
- **T052 ACTIVE**: CreditService Persistence — Unblocked by T059.S1
|
||||||
- **T053 PLANNED**: ChainFire Core Finalization — Remove OpenRaft, finish Gossip, clean debt
|
- **T053 PLANNED**: ChainFire Core Finalization — Remove OpenRaft, finish Gossip, clean debt
|
||||||
- **T054 PLANNED**: PlasmaVMC Ops — Hotplug, Reset, Update, Watch
|
- **T054 PLANNED**: PlasmaVMC Ops — Hotplug, Reset, Update, Watch
|
||||||
- **T055 PLANNED**: FiberLB Features — Maglev, L7, BGP
|
- **T055 PLANNED**: FiberLB Features — Maglev, L7, BGP
|
||||||
|
|
@ -55,9 +55,7 @@
|
||||||
- **T057 PLANNED**: k8shost Resource Management — IPAM & Tenant-aware Scheduler
|
- **T057 PLANNED**: k8shost Resource Management — IPAM & Tenant-aware Scheduler
|
||||||
- **T051 ACTIVE**: FiberLB Integration — S1-S3 complete; S4 Pending
|
- **T051 ACTIVE**: FiberLB Integration — S1-S3 complete; S4 Pending
|
||||||
- **T050 ACTIVE**: REST API — S1 Design complete; S2-S8 pending
|
- **T050 ACTIVE**: REST API — S1 Design complete; S2-S8 pending
|
||||||
- **T047 COMPLETE**: LightningSTOR S3 Compatibility — AWS CLI working (Auth bypassed)
|
- **T047 COMPLETE**: LightningSTOR S3 Compatibility — AWS CLI working (Auth bypassed - fixed in T058)
|
||||||
- **T042 BROKEN**: CreditService (MVP) — Code doesn't compile (missing chainfire_client.txn API)
|
|
||||||
- **MVP-Alpha STATUS**: BLOCKED — 3 critical failures (creditservice, chainfire, iam)
|
|
||||||
|
|
||||||
- **Next (2-4 weeks) — Integration & Enhancement:**
|
- **Next (2-4 weeks) — Integration & Enhancement:**
|
||||||
- **SDK**: gRPCクライアント一貫性 (T048)
|
- **SDK**: gRPCクライアント一貫性 (T048)
|
||||||
|
|
@ -117,6 +115,8 @@
|
||||||
- Falsify before expand; one decidable next step; stop with pride when wrong; Done = evidence.
|
- Falsify before expand; one decidable next step; stop with pride when wrong; Done = evidence.
|
||||||
|
|
||||||
## Maintenance & Change Log (append-only, one line each)
|
## Maintenance & Change Log (append-only, one line each)
|
||||||
|
- 2025-12-12 06:46 | peerA | T039 UNBLOCKED: User approved QEMU+VDE VM deployment instead of waiting for real hardware. Delegated to PeerB after T058.S2.
|
||||||
|
- 2025-12-12 06:41 | peerA | T059.S3 COMPLETE: iam visibility fixed (pub mod). MVP-Alpha ACHIEVED - all 3 audit issues resolved.
|
||||||
- 2025-12-12 06:39 | peerA | T060 CREATED: IAM Credential Service. T058.S2 Option B approved (env var MVP); proper IAM solution deferred to T060. Unblocks T039.
|
- 2025-12-12 06:39 | peerA | T060 CREATED: IAM Credential Service. T058.S2 Option B approved (env var MVP); proper IAM solution deferred to T060. Unblocks T039.
|
||||||
- 2025-12-12 06:37 | peerA | T059.S1+S2 COMPLETE: creditservice✓ chainfire✓. DELETE fix verified (2/3 tests pass, 1 flaky timing issue). iam S3 pending (1-line pub mod fix). PeerB pivoting to T058.S2.
|
- 2025-12-12 06:37 | peerA | T059.S1+S2 COMPLETE: creditservice✓ chainfire✓. DELETE fix verified (2/3 tests pass, 1 flaky timing issue). iam S3 pending (1-line pub mod fix). PeerB pivoting to T058.S2.
|
||||||
- 2025-12-12 06:35 | peerA | T059.S1 COMPLETE: PeerB fixed creditservice (CAS instead of txn). Foreman's "false alarm" claim WRONG - ran --lib only, not integration tests. chainfire/iam integration tests still fail. Approved Option A for DELETE fix.
|
- 2025-12-12 06:35 | peerA | T059.S1 COMPLETE: PeerB fixed creditservice (CAS instead of txn). Foreman's "false alarm" claim WRONG - ran --lib only, not integration tests. chainfire/iam integration tests still fail. Approved Option A for DELETE fix.
|
||||||
|
|
|
||||||
|
|
@ -671,7 +671,7 @@ mod tests {
|
||||||
build_canonical_request(method, uri, &headers, &body, signed_headers).unwrap();
|
build_canonical_request(method, uri, &headers, &body, signed_headers).unwrap();
|
||||||
|
|
||||||
// Build string to sign
|
// Build string to sign
|
||||||
let string_to_sign = build_string_to_sign(amz_date, credential_scope, &canonical_request);
|
let _string_to_sign = build_string_to_sign(amz_date, credential_scope, &canonical_request);
|
||||||
|
|
||||||
// Compute signature
|
// Compute signature
|
||||||
let signature = compute_sigv4_signature(
|
let signature = compute_sigv4_signature(
|
||||||
|
|
@ -706,4 +706,332 @@ mod tests {
|
||||||
assert_eq!(signature, signature2);
|
assert_eq!(signature, signature2);
|
||||||
assert_eq!(signature.len(), 64); // SHA256 hex = 64 chars
|
assert_eq!(signature.len(), 64); // SHA256 hex = 64 chars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Security Tests
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_invalid_auth_header_format() {
|
||||||
|
// Missing Credential field
|
||||||
|
let malformed1 = "AWS4-HMAC-SHA256 SignedHeaders=host, Signature=abc123";
|
||||||
|
assert!(parse_auth_header(malformed1).is_err());
|
||||||
|
|
||||||
|
// Missing SignedHeaders field
|
||||||
|
let malformed2 = "AWS4-HMAC-SHA256 Credential=KEY/scope, Signature=abc123";
|
||||||
|
assert!(parse_auth_header(malformed2).is_err());
|
||||||
|
|
||||||
|
// Missing Signature field
|
||||||
|
let malformed3 = "AWS4-HMAC-SHA256 Credential=KEY/scope, SignedHeaders=host";
|
||||||
|
assert!(parse_auth_header(malformed3).is_err());
|
||||||
|
|
||||||
|
// Wrong algorithm
|
||||||
|
let malformed4 = "AWS4-HMAC-SHA512 Credential=KEY/scope, SignedHeaders=host, Signature=abc";
|
||||||
|
assert!(parse_auth_header(malformed4).is_err());
|
||||||
|
|
||||||
|
// Empty string
|
||||||
|
assert!(parse_auth_header("").is_err());
|
||||||
|
|
||||||
|
// Random garbage
|
||||||
|
assert!(parse_auth_header("not-an-auth-header").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_signature_changes_with_secret_key() {
|
||||||
|
let method = "GET";
|
||||||
|
let uri = "/test-bucket/object";
|
||||||
|
let amz_date = "20231201T000000Z";
|
||||||
|
let credential_scope = "20231201/us-east-1/s3/aws4_request";
|
||||||
|
let signed_headers = "host;x-amz-date";
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("host", HeaderValue::from_static("s3.amazonaws.com"));
|
||||||
|
headers.insert("x-amz-date", HeaderValue::from_static("20231201T000000Z"));
|
||||||
|
|
||||||
|
let body = Bytes::new();
|
||||||
|
|
||||||
|
// Compute signature with first secret key
|
||||||
|
let sig1 = compute_sigv4_signature(
|
||||||
|
"secret1",
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Compute signature with different secret key
|
||||||
|
let sig2 = compute_sigv4_signature(
|
||||||
|
"secret2",
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signatures MUST be different
|
||||||
|
assert_ne!(sig1, sig2, "Signatures should differ with different secret keys");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_signature_changes_with_body() {
|
||||||
|
let secret_key = "test-secret-key";
|
||||||
|
let method = "PUT";
|
||||||
|
let uri = "/test-bucket/object";
|
||||||
|
let amz_date = "20231201T000000Z";
|
||||||
|
let credential_scope = "20231201/us-east-1/s3/aws4_request";
|
||||||
|
let signed_headers = "host;x-amz-date";
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("host", HeaderValue::from_static("s3.amazonaws.com"));
|
||||||
|
headers.insert("x-amz-date", HeaderValue::from_static("20231201T000000Z"));
|
||||||
|
|
||||||
|
// Signature with body1
|
||||||
|
let body1 = Bytes::from("original content");
|
||||||
|
let sig1 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body1,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signature with modified body
|
||||||
|
let body2 = Bytes::from("modified content");
|
||||||
|
let sig2 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body2,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signatures MUST be different
|
||||||
|
assert_ne!(sig1, sig2, "Signatures should differ with different bodies");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_signature_changes_with_uri() {
|
||||||
|
let secret_key = "test-secret-key";
|
||||||
|
let method = "GET";
|
||||||
|
let amz_date = "20231201T000000Z";
|
||||||
|
let credential_scope = "20231201/us-east-1/s3/aws4_request";
|
||||||
|
let signed_headers = "host;x-amz-date";
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("host", HeaderValue::from_static("s3.amazonaws.com"));
|
||||||
|
headers.insert("x-amz-date", HeaderValue::from_static("20231201T000000Z"));
|
||||||
|
|
||||||
|
let body = Bytes::new();
|
||||||
|
|
||||||
|
// Signature with uri1
|
||||||
|
let sig1 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
"/test-bucket/object1",
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signature with different URI
|
||||||
|
let sig2 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
"/test-bucket/object2",
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signatures MUST be different
|
||||||
|
assert_ne!(sig1, sig2, "Signatures should differ with different URIs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_signature_changes_with_headers() {
|
||||||
|
let secret_key = "test-secret-key";
|
||||||
|
let method = "GET";
|
||||||
|
let uri = "/test-bucket/object";
|
||||||
|
let amz_date = "20231201T000000Z";
|
||||||
|
let credential_scope = "20231201/us-east-1/s3/aws4_request";
|
||||||
|
let signed_headers = "host;x-amz-content-sha256;x-amz-date";
|
||||||
|
|
||||||
|
let mut headers1 = HeaderMap::new();
|
||||||
|
headers1.insert("host", HeaderValue::from_static("s3.amazonaws.com"));
|
||||||
|
headers1.insert("x-amz-date", HeaderValue::from_static("20231201T000000Z"));
|
||||||
|
headers1.insert("x-amz-content-sha256", HeaderValue::from_static("hash1"));
|
||||||
|
|
||||||
|
let mut headers2 = HeaderMap::new();
|
||||||
|
headers2.insert("host", HeaderValue::from_static("s3.amazonaws.com"));
|
||||||
|
headers2.insert("x-amz-date", HeaderValue::from_static("20231201T000000Z"));
|
||||||
|
headers2.insert("x-amz-content-sha256", HeaderValue::from_static("hash2"));
|
||||||
|
|
||||||
|
let body = Bytes::new();
|
||||||
|
|
||||||
|
let sig1 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
&headers1,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let sig2 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
uri,
|
||||||
|
&headers2,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signatures MUST be different
|
||||||
|
assert_ne!(sig1, sig2, "Signatures should differ with different header values");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_signature_changes_with_query_params() {
|
||||||
|
let secret_key = "test-secret-key";
|
||||||
|
let method = "GET";
|
||||||
|
let amz_date = "20231201T000000Z";
|
||||||
|
let credential_scope = "20231201/us-east-1/s3/aws4_request";
|
||||||
|
let signed_headers = "host;x-amz-date";
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("host", HeaderValue::from_static("s3.amazonaws.com"));
|
||||||
|
headers.insert("x-amz-date", HeaderValue::from_static("20231201T000000Z"));
|
||||||
|
|
||||||
|
let body = Bytes::new();
|
||||||
|
|
||||||
|
// URI with query param
|
||||||
|
let sig1 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
"/test-bucket/object?prefix=foo",
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// URI with different query param
|
||||||
|
let sig2 = compute_sigv4_signature(
|
||||||
|
secret_key,
|
||||||
|
method,
|
||||||
|
"/test-bucket/object?prefix=bar",
|
||||||
|
&headers,
|
||||||
|
amz_date,
|
||||||
|
&body,
|
||||||
|
credential_scope,
|
||||||
|
signed_headers,
|
||||||
|
"us-east-1",
|
||||||
|
"s3",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Signatures MUST be different
|
||||||
|
assert_ne!(sig1, sig2, "Signatures should differ with different query parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_credential_lookup_unknown_key() {
|
||||||
|
// Test that unknown access keys return the correct result
|
||||||
|
std::env::remove_var("S3_CREDENTIALS");
|
||||||
|
std::env::set_var("S3_ACCESS_KEY_ID", "known_key");
|
||||||
|
std::env::set_var("S3_SECRET_KEY", "known_secret");
|
||||||
|
|
||||||
|
let client = IamClient::new();
|
||||||
|
|
||||||
|
// Known key should be found in credentials map
|
||||||
|
assert_eq!(client.credentials.get("known_key"), Some(&"known_secret".to_string()));
|
||||||
|
|
||||||
|
// Unknown key should not be found
|
||||||
|
assert_eq!(client.credentials.get("unknown_key"), None);
|
||||||
|
|
||||||
|
std::env::remove_var("S3_ACCESS_KEY_ID");
|
||||||
|
std::env::remove_var("S3_SECRET_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_empty_credentials() {
|
||||||
|
// Test that IamClient falls back to dummy credentials when none provided
|
||||||
|
std::env::remove_var("S3_CREDENTIALS");
|
||||||
|
std::env::remove_var("S3_ACCESS_KEY_ID");
|
||||||
|
std::env::remove_var("S3_SECRET_KEY");
|
||||||
|
|
||||||
|
let client = IamClient::new();
|
||||||
|
|
||||||
|
// Should have dummy AWS example credentials (for testing only)
|
||||||
|
assert_eq!(client.credentials.len(), 1);
|
||||||
|
assert!(client.credentials.contains_key("AKIAIOSFODNN7EXAMPLE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_security_malformed_s3_credentials_env() {
|
||||||
|
// Test that malformed S3_CREDENTIALS are handled gracefully
|
||||||
|
|
||||||
|
// Missing colon separator
|
||||||
|
std::env::set_var("S3_CREDENTIALS", "key1_secret1,key2:secret2");
|
||||||
|
let client = IamClient::new();
|
||||||
|
// Should only parse the valid pair (key2:secret2)
|
||||||
|
assert_eq!(client.credentials.len(), 1);
|
||||||
|
assert!(client.credentials.contains_key("key2"));
|
||||||
|
|
||||||
|
// Empty pairs
|
||||||
|
std::env::set_var("S3_CREDENTIALS", "key1:secret1,,key2:secret2");
|
||||||
|
let client2 = IamClient::new();
|
||||||
|
// Should parse both valid pairs, skip empty
|
||||||
|
assert_eq!(client2.credentials.len(), 2);
|
||||||
|
|
||||||
|
std::env::remove_var("S3_CREDENTIALS");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Reference in a new issue