99#include " nix/util/signals.hh"
1010
1111#include " store-config-private.hh"
12+ #include < optional>
1213#if NIX_WITH_S3_SUPPORT
1314# include < aws/core/client/ClientConfiguration.h>
1415#endif
16+ #if NIX_WITH_CURL_S3
17+ # include " nix/store/aws-creds.hh"
18+ # include " nix/store/s3-url.hh"
19+ #endif
1520
1621#ifdef __linux__
1722# include " nix/util/linux-namespaces.hh"
@@ -426,6 +431,24 @@ struct curlFileTransfer : public FileTransfer
426431 curl_easy_setopt (req, CURLOPT_ERRORBUFFER, errbuf);
427432 errbuf[0 ] = 0 ;
428433
434+ // Set up username/password authentication if provided
435+ if (request.usernameAuth ) {
436+ curl_easy_setopt (req, CURLOPT_USERNAME, request.usernameAuth ->username .c_str ());
437+ if (request.usernameAuth ->password ) {
438+ curl_easy_setopt (req, CURLOPT_PASSWORD, request.usernameAuth ->password ->c_str ());
439+ }
440+ }
441+
442+ #if NIX_WITH_CURL_S3
443+ // Set up AWS SigV4 signing if this is an S3 request
444+ // Note: AWS SigV4 support guaranteed available (curl >= 7.75.0 checked at build time)
445+ // The username/password (access key ID and secret key) are set via the general
446+ // usernameAuth mechanism above.
447+ if (request.awsSigV4Provider ) {
448+ curl_easy_setopt (req, CURLOPT_AWS_SIGV4, request.awsSigV4Provider ->c_str ());
449+ }
450+ #endif
451+
429452 result.data .clear ();
430453 result.bodySize = 0 ;
431454 }
@@ -800,7 +823,11 @@ struct curlFileTransfer : public FileTransfer
800823
801824 void enqueueItem (std::shared_ptr<TransferItem> item)
802825 {
803- if (item->request .data && item->request .uri .scheme () != " http" && item->request .uri .scheme () != " https" )
826+ if (item->request .data && item->request .uri .scheme () != " http" && item->request .uri .scheme () != " https"
827+ #if NIX_WITH_CURL_S3
828+ && item->request .uri .scheme () != " s3"
829+ #endif
830+ )
804831 throw nix::Error (" uploading to '%s' is not supported" , item->request .uri .to_string ());
805832
806833 {
@@ -818,9 +845,15 @@ struct curlFileTransfer : public FileTransfer
818845 {
819846 /* Ugly hack to support s3:// URIs. */
820847 if (request.uri .scheme () == " s3" ) {
848+ #if NIX_WITH_CURL_S3
849+ // New curl-based S3 implementation
850+ auto modifiedRequest = request;
851+ modifiedRequest.setupForS3 ();
852+ enqueueItem (std::make_shared<TransferItem>(*this , std::move (modifiedRequest), std::move (callback)));
853+ #elif NIX_WITH_S3_SUPPORT
854+ // Old AWS SDK-based implementation
821855 // FIXME: do this on a worker thread
822856 try {
823- #if NIX_WITH_S3_SUPPORT
824857 auto parsed = ParsedS3URL::parse (request.uri .parsed ());
825858
826859 std::string profile = parsed.profile .value_or (" " );
@@ -838,13 +871,12 @@ struct curlFileTransfer : public FileTransfer
838871 res.data = std::move (*s3Res.data );
839872 res.urls .push_back (request.uri .to_string ());
840873 callback (std::move (res));
841- #else
842- throw nix::Error (
843- " cannot download '%s' because Nix is not built with S3 support" , request.uri .to_string ());
844- #endif
845874 } catch (...) {
846875 callback.rethrow ();
847876 }
877+ #else
878+ throw nix::Error (" cannot download '%s' because Nix is not built with S3 support" , request.uri .to_string ());
879+ #endif
848880 return ;
849881 }
850882
@@ -872,6 +904,41 @@ ref<FileTransfer> makeFileTransfer()
872904 return makeCurlFileTransfer ();
873905}
874906
907+ #if NIX_WITH_CURL_S3
908+ void FileTransferRequest::setupForS3 ()
909+ {
910+ auto parsedS3 = ParsedS3URL::parse (uri.parsed ());
911+ // Update the request URI to use HTTPS
912+ uri = parsedS3.toHttpsUrl ();
913+ // This gets used later in a curl setopt
914+ awsSigV4Provider = " aws:amz:" + parsedS3.region .value_or (" us-east-1" ) + " :s3" ;
915+ // check if the request already has pre-resolved credentials
916+ std::optional<std::string> sessionToken;
917+ if (usernameAuth) {
918+ debug (" Using pre-resolved AWS credentials from parent process" );
919+ sessionToken = preResolvedAwsSessionToken;
920+ } else {
921+ std::string profile = parsedS3.profile .value_or (" " );
922+ try {
923+ auto creds = getAwsCredentials (profile);
924+ usernameAuth = UsernameAuth{
925+ .username = creds.accessKeyId ,
926+ .password = creds.secretAccessKey ,
927+ };
928+ sessionToken = creds.sessionToken ;
929+ } catch (const AwsAuthError & e) {
930+ warn (" AWS authentication failed for S3 request %s: %s" , uri, e.what ());
931+ // Invalidate the cached credentials so next request will retry
932+ invalidateAwsCredentials (profile);
933+ // Continue without authentication - might be a public bucket
934+ return ;
935+ }
936+ }
937+ if (sessionToken)
938+ headers.emplace_back (" x-amz-security-token" , *sessionToken);
939+ }
940+ #endif
941+
875942std::future<FileTransferResult> FileTransfer::enqueueFileTransfer (const FileTransferRequest & request)
876943{
877944 auto promise = std::make_shared<std::promise<FileTransferResult>>();
0 commit comments