minio / minio-cpp

MinIO C++ Client SDK for Amazon S3 Compatible Cloud Storage
https://minio-cpp.min.io/
Apache License 2.0
135 stars 56 forks source link

localtime function and DST #132

Closed andy3469 closed 7 months ago

andy3469 commented 7 months ago

There is a problem of RequestTimeTooSkewed when you try to connect to a server while the timezone is in DST mode.

When you add x-amx-date to the request header, the function localtime is called.

I know that all the communication are done in UTC as noted in the PR #115. The thing is, when the PC is set in DST (daylight), localtime add 1 hour to the returned time. If I disable DST, it's working correctly.

PS: I'm working on Windows

balamurugana commented 7 months ago

@andy3469 looks like MS Windows issue. Can you do Client.Debug(true) and share the output?

andy3469 commented 7 months ago
* !!! WARNING !!!
* This is a debug build of libcurl, do not use in production.
* STATE: INIT => CONNECT handle 0x2cf9ea893f8; line 1910
* Curl_client_reset(), clear readers
* Added connection 0. The cache now contains 1 members
* STATE: CONNECT => RESOLVING handle 0x2cf9ea893f8; line 1954
* Host localhost:443 was resolved.
* IPv6: (none)
* IPv4: 127.0.0.1
* STATE: RESOLVING => CONNECTING handle 0x2cf9ea893f8; line 2028
*   Trying 127.0.0.1:443...
* Connected to localhost (127.0.0.1) port 443
* schannel: SSL/TLS connection with localhost port 443 (step 1/3)
* Didn't find Session ID in cache for host HTTPS://localhost:443
* schannel: checking server certificate revocation
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* schannel: sending initial handshake data: sending 289 bytes.
* schannel: sent initial handshake data: sent 289 bytes
* schannel: SSL/TLS connection with localhost port 443 (step 2/3)
* schannel: failed to receive handshake, need more data
* schannel: SSL/TLS connection with localhost port 443 (step 2/3)
* schannel: encrypted data got 2920
* schannel: encrypted data buffer: offset 2920 length 4096
* schannel: encrypted data length: 132
* schannel: encrypted data buffer: offset 132 length 4096
* schannel: received incomplete message, need more data
* schannel: SSL/TLS connection with localhost port 443 (step 2/3)
* schannel: encrypted data got 228
* schannel: encrypted data buffer: offset 360 length 4096
* schannel: sending next handshake data: sending 80 bytes.
* schannel: SSL/TLS handshake complete
* schannel: SSL/TLS connection with localhost port 443 (step 3/3)
* ALPN: server accepted http/1.1
* Didn't find Session ID in cache for host HTTPS://localhost:443
* Added Session ID to cache for HTTPS://localhost:443 [server]
* schannel: stored credential handle in session cache
* using HTTP/1.x
* STATE: CONNECTING => PROTOCONNECT handle 0x2cf9ea893f8; line 2072
* STATE: PROTOCONNECT => DO handle 0x2cf9ea893f8; line 2101
> GET /releases?location= HTTP/1.1
Host: localhost
Accept: */*
Authorization: AWS4-HMAC-SHA256 Credential=creds/20240410/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=6cd33d8d5e4f978b9e483cc95eb4c3fabae726cfc3a0d807386793fbba066923
User-Agent: MinIO (windows; x86_64) minio-cpp/MINIO_CPP_MAJOR_VERSION.MINIO_CPP_MINOR_VERSION.MINIO_CPP_PATCH_VERSION
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date: 20240410T124443Z

* STATE: DO => DID handle 0x2cf9ea893f8; line 2197
* STATE: DID => PERFORMING handle 0x2cf9ea893f8; line 2315
* Request completely sent off
* schannel: client wants to read 16384 bytes
* schannel: encdata_buffer resized 17408
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: encrypted data got 287
* schannel: encrypted data buffer: offset 287 length 17408
* schannel: decrypted data length: 0
* schannel: decrypted data added: 0
* schannel: decrypted cached: offset 0 length 16384
* schannel: encrypted data length: 287
* schannel: encrypted cached: offset 287 length 17408
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection with localhost port 443 (step 2/3)
* schannel: encrypted data buffer: offset 287 length 17408
* schannel: SSL/TLS handshake complete
* schannel: SSL/TLS connection with localhost port 443 (step 3/3)
* Found Session ID in cache for host HTTPS://localhost:443
* schannel: SSL/TLS connection renegotiated
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: decrypted data buffer: offset 0 length 16384
* schannel: schannel_recv cleanup
* schannel: client wants to read 16384 bytes
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: encrypted data got 287
* schannel: encrypted data buffer: offset 287 length 17408
* schannel: decrypted data length: 0
* schannel: decrypted data added: 0
* schannel: decrypted cached: offset 0 length 16384
* schannel: encrypted data length: 287
* schannel: encrypted cached: offset 287 length 17408
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection with localhost port 443 (step 2/3)
* schannel: encrypted data buffer: offset 287 length 17408
* schannel: SSL/TLS handshake complete
* schannel: SSL/TLS connection with localhost port 443 (step 3/3)
* Found Session ID in cache for host HTTPS://localhost:443
* schannel: SSL/TLS connection renegotiated
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: decrypted data buffer: offset 0 length 16384
* schannel: schannel_recv cleanup
* schannel: client wants to read 16384 bytes
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: encrypted data got 953
* schannel: encrypted data buffer: offset 953 length 17408
* schannel: decrypted data length: 931
* schannel: decrypted data added: 931
* schannel: decrypted cached: offset 931 length 16384
* schannel: encrypted data buffer: offset 0 length 17408
* schannel: decrypted data buffer: offset 931 length 16384
* schannel: schannel_recv cleanup
* schannel: decrypted data returned 931
* schannel: decrypted data buffer: offset 0 length 16384
* HTTP 1.1 or later with persistent connection
< HTTP/1.1 403 Forbidden
< Server: nginx/1.18.0
< Date: Wed, 10 Apr 2024 11:44:42 GMT
< Content-Type: application/xml
< Content-Length: 361
< Connection: keep-alive
< Keep-Alive: timeout=75
< Accept-Ranges: bytes
< Strict-Transport-Security: max-age=31536000; includeSubDomains
< Vary: Origin
< X-Amz-Bucket-Region: eu-west-3
< X-Amz-Id-2: dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
< X-Amz-Request-Id: 17C4E894790B4050
< X-Content-Type-Options: nosniff
< X-Xss-Protection: 1; mode=block
< Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
< 
* STATE: PERFORMING => DONE handle 0x2cf9ea893f8; line 2512
* multi_done[DONE]: status: 0 prem: 0 done: 0
* Curl_client_reset(), clear readers
* Connection #0 to host localhost left intact
* Curl_client_cleanup()

I just changed all the IPs and url to another thing because of security

Also for your information, I tested the connection with DST and the python SDK and it's working correctly. Only the C++ SDK is not working correctly.

ijoseph commented 7 months ago

Wow, topical. I just fought through this on macOS last night. This fixed it:

diff --git a/src/utils.cc b/src/utils.cc
index 5204547..f3418be 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -337,7 +337,9 @@ UtcTime UtcTime::Now() {
   auto usec_now = std::chrono::system_clock::now().time_since_epoch() /
                   std::chrono::microseconds(1);
   auto secs_local = static_cast<time_t>(usec_now / 1000000);
-  auto secs_utc = std::mktime(std::gmtime(&secs_local));
+  auto tm_time = std::gmtime(&secs_local);
+  tm_time->tm_isdst = - 1;
+  auto secs_utc = std::mktime(tm_time);
   return UtcTime(secs_utc, static_cast<long>(usec_now % 1000000));
 }

The issue, as they mention here, is that the std::tm returned by std::gmtime has the tm_isdst flag set to 0, which then causes std::mktime to wrongfully (when DST is, in fact, in effect in one's local timezone) adjust the time when converting this back into a std::time_t.

I set it to -1 because:

A negative value of time->tm_isdst causes mktime to attempt to determine if Daylight Saving Time was in effect.

Admittedly, it's not clear exactly how mktime does this, but it seemed to do it correctly on my system.

balamurugana commented 7 months ago

@andy3469 Can you confirm whether the PR https://github.com/minio/minio-cpp/pull/133 fixes the issue?

andy3469 commented 7 months ago

Yes the PR #133 fixes the issue correctly