diff --git a/doc/xml/release/2020s/2026/2.58.0.xml b/doc/xml/release/2020s/2026/2.58.0.xml
index 2024b844e..3f9ff6849 100644
--- a/doc/xml/release/2020s/2026/2.58.0.xml
+++ b/doc/xml/release/2020s/2026/2.58.0.xml
@@ -57,6 +57,18 @@
Support for Azure managed identities.
+
+
+
+
+
+
+
+
+
+ Experimental support for S3 EKS pod identity.
+
+
diff --git a/doc/xml/release/contributor.xml b/doc/xml/release/contributor.xml
index d2fa8ad4d..048a30e18 100644
--- a/doc/xml/release/contributor.xml
+++ b/doc/xml/release/contributor.xml
@@ -935,6 +935,11 @@
philrhurst
+
+ Pierre BOUTELOUP
+ wolrajhti
+
+
Pierre Ducroquet
PierreDucroquet
diff --git a/src/build/config/config.yaml b/src/build/config/config.yaml
index 857c725d8..ce4c2bc0d 100644
--- a/src/build/config/config.yaml
+++ b/src/build/config/config.yaml
@@ -2311,6 +2311,7 @@ option:
- shared
- auto
- web-id
+ - pod-id
repo-s3-key:
section: global
diff --git a/src/config/config.auto.h b/src/config/config.auto.h
index 6a7c3f181..c1ce532c8 100644
--- a/src/config/config.auto.h
+++ b/src/config/config.auto.h
@@ -284,6 +284,8 @@ Option value constants
#define CFGOPTVAL_REPO_S3_KEY_TYPE_AUTO STRID5("auto", 0x7d2a10)
#define CFGOPTVAL_REPO_S3_KEY_TYPE_AUTO_Z "auto"
+#define CFGOPTVAL_REPO_S3_KEY_TYPE_POD_ID STRID5("pod-id", 0x89d91f00)
+#define CFGOPTVAL_REPO_S3_KEY_TYPE_POD_ID_Z "pod-id"
#define CFGOPTVAL_REPO_S3_KEY_TYPE_SHARED STRID5("shared", 0x85905130)
#define CFGOPTVAL_REPO_S3_KEY_TYPE_SHARED_Z "shared"
#define CFGOPTVAL_REPO_S3_KEY_TYPE_WEB_ID STRID5("web-id", 0x89d88b70)
diff --git a/src/config/parse.auto.c.inc b/src/config/parse.auto.c.inc
index 0ff01c5e0..8dd027b4e 100644
--- a/src/config/parse.auto.c.inc
+++ b/src/config/parse.auto.c.inc
@@ -109,6 +109,7 @@ static const StringPubConst parseRuleValueStr[] =
PARSE_RULE_STRPUB("pause"), // val/str
PARSE_RULE_STRPUB("pg"), // val/str
PARSE_RULE_STRPUB("pgbackrest"), // val/str
+ PARSE_RULE_STRPUB("pod-id"), // val/str
PARSE_RULE_STRPUB("posix"), // val/str
PARSE_RULE_STRPUB("postgres"), // val/str
PARSE_RULE_STRPUB("prefer"), // val/str
@@ -243,6 +244,7 @@ typedef enum
parseRuleValStrQT_pause_QT, // val/str/enum
parseRuleValStrQT_pg_QT, // val/str/enum
parseRuleValStrQT_pgbackrest_QT, // val/str/enum
+ parseRuleValStrQT_pod_DS_id_QT, // val/str/enum
parseRuleValStrQT_posix_QT, // val/str/enum
parseRuleValStrQT_postgres_QT, // val/str/enum
parseRuleValStrQT_prefer_QT, // val/str/enum
@@ -318,6 +320,7 @@ static const StringId parseRuleValueStrId[] =
STRID5("path", 0x450300), // val/strid
STRID5("pause", 0x59d4300), // val/strid
STRID5("pg", 0xf00), // val/strid
+ STRID5("pod-id", 0x89d91f00), // val/strid
STRID5("posix", 0x184cdf00), // val/strid
STRID5("prefer", 0x245316500), // val/strid
STRID5("preserve", 0x2da45996500), // val/strid
@@ -383,6 +386,7 @@ static const uint8_t parseRuleValueStrIdStrMap[] =
parseRuleValStrQT_path_QT, // val/strid/strmap
parseRuleValStrQT_pause_QT, // val/strid/strmap
parseRuleValStrQT_pg_QT, // val/strid/strmap
+ parseRuleValStrQT_pod_DS_id_QT, // val/strid/strmap
parseRuleValStrQT_posix_QT, // val/strid/strmap
parseRuleValStrQT_prefer_QT, // val/strid/strmap
parseRuleValStrQT_preserve_QT, // val/strid/strmap
@@ -448,6 +452,7 @@ typedef enum
parseRuleValStrIdPath, // val/strid/enum
parseRuleValStrIdPause, // val/strid/enum
parseRuleValStrIdPg, // val/strid/enum
+ parseRuleValStrIdPodId, // val/strid/enum
parseRuleValStrIdPosix, // val/strid/enum
parseRuleValStrIdPrefer, // val/strid/enum
parseRuleValStrIdPreserve, // val/strid/enum
@@ -8033,6 +8038,7 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
PARSE_RULE_VAL_STRID(Shared), // opt/repo-s3-key-type
PARSE_RULE_VAL_STRID(Auto), // opt/repo-s3-key-type
PARSE_RULE_VAL_STRID(WebId), // opt/repo-s3-key-type
+ PARSE_RULE_VAL_STRID(PodId), // opt/repo-s3-key-type
), // opt/repo-s3-key-type
// opt/repo-s3-key-type
PARSE_RULE_OPTIONAL_DEFAULT // opt/repo-s3-key-type
diff --git a/src/storage/s3/helper.c b/src/storage/s3/helper.c
index 117501206..9c7d9131e 100644
--- a/src/storage/s3/helper.c
+++ b/src/storage/s3/helper.c
@@ -57,7 +57,8 @@ storageS3Helper(const unsigned int repoIdx, const bool write, StoragePathExpress
// Get role and token
const StorageS3KeyType keyType = (StorageS3KeyType)cfgOptionIdxStrId(cfgOptRepoS3KeyType, repoIdx);
const String *role = cfgOptionIdxStrNull(cfgOptRepoS3Role, repoIdx);
- const String *webIdTokenFile = NULL;
+ const String *tokenFile = NULL;
+ const String *credUrl = NULL;
// If web identity authentication then load the role and token filename from environment variables documented here:
// https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html
@@ -79,7 +80,29 @@ storageS3Helper(const unsigned int repoIdx, const bool write, StoragePathExpress
}
role = strNewZ(roleZ);
- webIdTokenFile = strNewZ(webIdTokenFileZ);
+ tokenFile = strNewZ(webIdTokenFileZ);
+ }
+ // If pod identity authentication then load the credentials url and token filename from environment variables documented
+ // here: https://docs.aws.amazon.com/eks/latest/userguide/pod-id-how-it-works.html
+ else if (keyType == storageS3KeyTypePodId)
+ {
+ #define S3_ENV_AWS_CONTAINER_CREDENTIALS_FULL_URI "AWS_CONTAINER_CREDENTIALS_FULL_URI"
+ #define S3_ENV_AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"
+
+ const char *const credUrlZ = getenv(S3_ENV_AWS_CONTAINER_CREDENTIALS_FULL_URI);
+ const char *const podIdTokenFileZ = getenv(S3_ENV_AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE);
+
+ if (credUrlZ == NULL || podIdTokenFileZ == NULL)
+ {
+ THROW_FMT(
+ OptionInvalidError,
+ "option '%s' is '" CFGOPTVAL_REPO_S3_KEY_TYPE_POD_ID_Z "' but '" S3_ENV_AWS_CONTAINER_CREDENTIALS_FULL_URI "'"
+ " and '" S3_ENV_AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE "' are not set",
+ cfgOptionIdxName(cfgOptRepoS3KeyType, repoIdx));
+ }
+
+ credUrl = strNewZ(credUrlZ);
+ tokenFile = strNewZ(podIdTokenFileZ);
}
MEM_CONTEXT_PRIOR_BEGIN()
@@ -90,7 +113,7 @@ storageS3Helper(const unsigned int repoIdx, const bool write, StoragePathExpress
(StorageS3UriStyle)cfgOptionIdxStrId(cfgOptRepoS3UriStyle, repoIdx), cfgOptionIdxStr(cfgOptRepoS3Region, repoIdx),
keyType, cfgOptionIdxStrNull(cfgOptRepoS3Key, repoIdx), cfgOptionIdxStrNull(cfgOptRepoS3KeySecret, repoIdx),
cfgOptionIdxStrNull(cfgOptRepoS3Token, repoIdx), cfgOptionIdxStrNull(cfgOptRepoS3KmsKeyId, repoIdx),
- cfgOptionIdxStrNull(cfgOptRepoS3SseCustomerKey, repoIdx), role, webIdTokenFile,
+ cfgOptionIdxStrNull(cfgOptRepoS3SseCustomerKey, repoIdx), role, tokenFile, credUrl,
(size_t)cfgOptionIdxUInt64(cfgOptRepoStorageUploadChunkSize, repoIdx),
cfgOptionIdxKvNull(cfgOptRepoStorageTag, repoIdx), host, port, ioTimeoutMs(), protocolType,
cfgOptionIdxBool(cfgOptRepoStorageVerifyTls, repoIdx), cfgOptionIdxStrNull(cfgOptRepoStorageCaFile, repoIdx),
diff --git a/src/storage/s3/storage.c b/src/storage/s3/storage.c
index 8e653744a..c0ded9329 100644
--- a/src/storage/s3/storage.c
+++ b/src/storage/s3/storage.c
@@ -117,8 +117,9 @@ struct StorageS3
// For retrieving temporary security credentials
HttpClient *credHttpClient; // HTTP client to service credential requests
const String *credHost; // Credentials host
+ const String *credPath; // Credential url path
const String *credRole; // Role to use for credential requests
- const String *webIdTokenFile; // File containing token to use for web-id credential requests
+ const String *tokenFile; // File with token to use for web-id/pod-id credential requests
time_t credExpirationTime; // Time the temporary credentials expire
// Current signing key and date it is valid for
@@ -395,8 +396,7 @@ storageS3AuthWebId(StorageS3 *const this, const HttpHeader *const header)
MEM_CONTEXT_TEMP_BEGIN()
{
// Load the token from the given file for each request since the token may be updated during execution
- const String *const webIdToken = strNewBuf(
- storageGetP(storageNewReadP(storagePosixNewP(FSLASH_STR), this->webIdTokenFile)));
+ const String *const webIdToken = strNewBuf(storageGetP(storageNewReadP(storagePosixNewP(FSLASH_STR), this->tokenFile)));
// Get credentials
HttpQuery *const query = httpQueryNewP();
@@ -440,6 +440,56 @@ storageS3AuthWebId(StorageS3 *const this, const HttpHeader *const header)
FUNCTION_LOG_RETURN_VOID();
}
+/***********************************************************************************************************************************
+Automatically get credentials for an associated pod identity
+
+The AWS documentation does not appear to provide a clear example of how to perform this authorization without using their SDK but
+this article proved helpful: https://securitylabs.datadoghq.com/articles/eks-pod-identity-deep-dive
+***********************************************************************************************************************************/
+static void
+storageS3AuthPodId(StorageS3 *const this, const HttpHeader *const header)
+{
+ FUNCTION_LOG_BEGIN(logLevelDebug);
+ FUNCTION_LOG_PARAM(STORAGE_S3, this);
+ FUNCTION_LOG_PARAM(HTTP_HEADER, header);
+ FUNCTION_LOG_END();
+
+ MEM_CONTEXT_TEMP_BEGIN()
+ {
+ // Load the token from the given file for each request since the token may be updated during execution
+ const String *const podIdToken = strNewBuf(storageGetP(storageNewReadP(storagePosixNewP(FSLASH_STR), this->tokenFile)));
+
+ // Get credentials
+ HttpRequest *const request = httpRequestNewP(
+ this->credHttpClient, HTTP_VERB_GET_STR, this->credPath,
+ .header = httpHeaderAdd(httpHeaderDup(header, NULL), HTTP_HEADER_AUTHORIZATION_STR, podIdToken));
+ HttpResponse *const response = httpRequestResponse(request, true);
+
+ CHECK(FormatError, httpResponseCode(response) != HTTP_RESPONSE_CODE_NOT_FOUND, "invalid response code");
+ const KeyValue *const kvResponse = varKv(jsonToVar(strNewBuf(httpResponseContent(response))));
+
+ // Copy credentials
+ MEM_CONTEXT_OBJ_BEGIN(this)
+ {
+ this->accessKey = strDup(varStr(kvGet(kvResponse, VARSTRDEF("AccessKeyId"))));
+ CHECK(FormatError, this->accessKey != NULL, "access key missing");
+ this->secretAccessKey = strDup(varStr(kvGet(kvResponse, VARSTRDEF("SecretAccessKey"))));
+ CHECK(FormatError, this->secretAccessKey != NULL, "secret access key missing");
+ this->securityToken = strDup(varStr(kvGet(kvResponse, VARSTRDEF("Token"))));
+ CHECK(FormatError, this->securityToken != NULL, "token missing");
+ }
+ MEM_CONTEXT_OBJ_END();
+
+ // Update expiration time
+ const String *const credExpirationTime = varStr(kvGet(kvResponse, VARSTRDEF("Expiration")));
+ CHECK(FormatError, credExpirationTime != NULL, "expiration missing");
+ this->credExpirationTime = storageS3CvtTime(credExpirationTime);
+ }
+ MEM_CONTEXT_TEMP_END();
+
+ FUNCTION_LOG_RETURN_VOID();
+}
+
/***********************************************************************************************************************************
Process S3 request
***********************************************************************************************************************************/
@@ -530,6 +580,11 @@ storageS3RequestAsync(StorageS3 *const this, const String *const verb, const Str
storageS3AuthAuto(this, credHeader);
break;
+ // Pod identity authentication
+ case storageS3KeyTypePodId:
+ storageS3AuthPodId(this, credHeader);
+ break;
+
// Web identity authentication
default:
{
@@ -1185,9 +1240,9 @@ storageS3New(
const String *const bucket, const String *const endPoint, const StorageS3UriStyle uriStyle, const String *const region,
const StorageS3KeyType keyType, const String *const accessKey, const String *const secretAccessKey,
const String *const securityToken, const String *const kmsKeyId, const String *sseCustomerKey, const String *const credRole,
- const String *const webIdTokenFile, const size_t partSize, const KeyValue *const tag, const String *host,
- const unsigned int port, const TimeMSec timeout, const HttpProtocolType protocolType, const bool verifyPeer,
- const String *const caFile, const String *const caPath, const bool requesterPays)
+ const String *const tokenFile, const String *const credUrl, const size_t partSize, const KeyValue *const tag,
+ const String *host, const unsigned int port, const TimeMSec timeout, const HttpProtocolType protocolType,
+ const bool verifyPeer, const String *const caFile, const String *const caPath, const bool requesterPays)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, path);
@@ -1205,7 +1260,8 @@ storageS3New(
FUNCTION_TEST_PARAM(STRING, kmsKeyId);
FUNCTION_TEST_PARAM(STRING, sseCustomerKey);
FUNCTION_TEST_PARAM(STRING, credRole);
- FUNCTION_TEST_PARAM(STRING, webIdTokenFile);
+ FUNCTION_TEST_PARAM(STRING, tokenFile);
+ FUNCTION_TEST_PARAM(STRING, credUrl);
FUNCTION_LOG_PARAM(SIZE, partSize);
FUNCTION_LOG_PARAM(KEY_VALUE, tag);
FUNCTION_LOG_PARAM(STRING, host);
@@ -1291,10 +1347,10 @@ storageS3New(
{
ASSERT(accessKey == NULL && secretAccessKey == NULL && securityToken == NULL);
ASSERT(credRole != NULL);
- ASSERT(webIdTokenFile != NULL);
+ ASSERT(tokenFile != NULL);
this->credRole = strDup(credRole);
- this->webIdTokenFile = strDup(webIdTokenFile);
+ this->tokenFile = strDup(tokenFile);
this->credHost = S3_STS_HOST_STR;
this->credExpirationTime = time(NULL);
this->credHttpClient = httpClientNew(
@@ -1306,6 +1362,23 @@ storageS3New(
break;
}
+ // Create the HTTP client used to retrieve pod identity security credentials
+ case storageS3KeyTypePodId:
+ {
+ ASSERT(accessKey == NULL && secretAccessKey == NULL && securityToken == NULL);
+ ASSERT(credUrl != NULL);
+ ASSERT(tokenFile != NULL);
+
+ const HttpUrl *const url = httpUrlNewParseP(credUrl);
+ this->tokenFile = strDup(tokenFile);
+ this->credHost = httpUrlHost(url);
+ this->credPath = strDup(httpUrlPath(url));
+ this->credExpirationTime = time(NULL);
+ this->credHttpClient = httpClientNew(sckClientNew(this->credHost, httpUrlPort(url), timeout, timeout), timeout);
+
+ break;
+ }
+
// Set shared key credentials
default:
{
diff --git a/src/storage/s3/storage.h b/src/storage/s3/storage.h
index 3c2d02ea0..dfa5766c3 100644
--- a/src/storage/s3/storage.h
+++ b/src/storage/s3/storage.h
@@ -20,6 +20,7 @@ typedef enum
storageS3KeyTypeShared = STRID5("shared", 0x85905130),
storageS3KeyTypeAuto = STRID5("auto", 0x7d2a10),
storageS3KeyTypeWebId = STRID5("web-id", 0x89d88b70),
+ storageS3KeyTypePodId = STRID5("pod-id", 0x89d91f00),
} StorageS3KeyType;
/***********************************************************************************************************************************
@@ -38,8 +39,8 @@ FN_EXTERN Storage *storageS3New(
const String *path, bool write, time_t targetTime, StoragePathExpressionCallback pathExpressionFunction, const String *bucket,
const String *endPoint, StorageS3UriStyle uriStyle, const String *region, StorageS3KeyType keyType, const String *accessKey,
const String *secretAccessKey, const String *securityToken, const String *kmsKeyId, const String *sseCustomerKey,
- const String *credRole, const String *webIdTokenFile, size_t partSize, const KeyValue *tag, const String *host,
- unsigned int port, TimeMSec timeout, HttpProtocolType protocolType, bool verifyPeer, const String *caFile, const String *caPath,
- bool requesterPays);
+ const String *credRole, const String *tokenFile, const String *credUrl, size_t partSize, const KeyValue *tag,
+ const String *host, unsigned int port, TimeMSec timeout, HttpProtocolType protocolType, bool verifyPeer, const String *caFile,
+ const String *caPath, bool requesterPays);
#endif
diff --git a/test/src/common/harnessHost.c b/test/src/common/harnessHost.c
index 071f2463b..c9eb6881d 100644
--- a/test/src/common/harnessHost.c
+++ b/test/src/common/harnessHost.c
@@ -784,7 +784,7 @@ hrnHostConfig(HrnHost *const this)
this->pub.repo1Storage = storageS3New(
hrnHostRepo1Path(this), true, 0, NULL, STRDEF(HRN_HOST_S3_BUCKET), STRDEF(HRN_HOST_S3_ENDPOINT),
storageS3UriStyleHost, STR(HRN_HOST_S3_REGION), storageS3KeyTypeShared, STRDEF(HRN_HOST_S3_ACCESS_KEY),
- STRDEF(HRN_HOST_S3_ACCESS_SECRET_KEY), NULL, NULL, NULL, NULL, NULL, 5 * 1024 * 1024, NULL,
+ STRDEF(HRN_HOST_S3_ACCESS_SECRET_KEY), NULL, NULL, NULL, NULL, NULL, NULL, 5 * 1024 * 1024, NULL,
hrnHostIp(s3), 443, ioTimeoutMs(), httpProtocolTypeHttps, false, NULL, NULL, NULL);
}
MEM_CONTEXT_OBJ_END();
diff --git a/test/src/module/storage/s3Test.c b/test/src/module/storage/s3Test.c
index d7a311f7a..92b7cf4fd 100644
--- a/test/src/module/storage/s3Test.c
+++ b/test/src/module/storage/s3Test.c
@@ -75,6 +75,7 @@ typedef struct TestRequestParam
const char *content;
const char *accessKey;
const char *securityToken;
+ const char *authorization;
const char *range;
const char *kms;
const char *sseC;
@@ -149,6 +150,10 @@ testRequest(IoWrite *write, Storage *s3, const char *verb, const char *path, Tes
strCatZ(request, ",Signature=????????????????????????????????????????????????????????????????\r\n");
}
+ // Add authorization
+ if (param.authorization != NULL)
+ strCatFmt(request, "authorization:%s\r\n", param.authorization);
+
// Add content-length
strCatFmt(request, "content-length:%zu\r\n", param.content != NULL ? strlen(param.content) : 0);
@@ -1072,7 +1077,7 @@ testRun(void)
driver = (StorageS3 *)storageDriver(s3);
TEST_RESULT_STR_Z(driver->credRole, TEST_SERVICE_ROLE, "check role");
- TEST_RESULT_STR_Z(driver->webIdTokenFile, TEST_SERVICE_TOKEN_FILE, "check token file");
+ TEST_RESULT_STR_Z(driver->tokenFile, TEST_SERVICE_TOKEN_FILE, "check token file");
// Set partSize to a small value for testing
driver->partSize = 16;
@@ -1125,9 +1130,6 @@ testRun(void)
TEST_RESULT_UINT(info.size, 9999, "check exists");
TEST_RESULT_INT(info.timeModified, 1445412480, "check time");
- // Auth service no longer needed
- hrnServerScriptEnd(auth);
-
// -----------------------------------------------------------------------------------------------------------------
TEST_TITLE("write zero-length file (without kms)");
@@ -1156,10 +1158,71 @@ testRun(void)
storageListP(s3, STRDEF("/"), .errorOnMissing = true), AssertError,
"assertion '!param.errorOnMissing || storageFeature(this, storageFeaturePath)' failed");
+ // -----------------------------------------------------------------------------------------------------------------
+ TEST_TITLE("switch to pod-id authentication");
+
+ hrnServerScriptClose(service);
+
+ #define TEST_PODID_TOKEN "TOKEN-PODID"
+ #define TEST_PODID_TOKEN_FILE TEST_PATH "/pod-id-token"
+ #define TEST_PODID_GET "/v1/credentials"
+ // {uncrustify_off - comment inside string}
+ #define TEST_PODID_RESPONSE \
+ "{\n" \
+ " \"AccessKeyId\": \"gg\",\n" \
+ " \"SecretAccessKey\": \"hh\",\n" \
+ " \"Token\": \"mm\",\n" \
+ " \"AccountId\":\"012345678901\",\n" \
+ " \"Expiration\": \"%s\"\n" \
+ "}"
+ // {uncrustify_on}
+
+ HRN_STORAGE_PUT_Z(storagePosixNewP(TEST_PATH_STR, .write = true), TEST_PODID_TOKEN_FILE, TEST_PODID_TOKEN);
+
+ argList = strLstDup(commonArgList);
+ hrnCfgArgRawFmt(argList, cfgOptRepoStorageHost, "%s:%u", strZ(host), testPort);
+ hrnCfgArgRawStrId(argList, cfgOptRepoS3KeyType, storageS3KeyTypePodId);
+ HRN_CFG_LOAD(cfgCmdArchivePush, argList);
+
+ TEST_ERROR(
+ storageRepoGet(0, true), OptionInvalidError,
+ "option 'repo1-s3-key-type' is 'pod-id' but 'AWS_CONTAINER_CREDENTIALS_FULL_URI' and"
+ " 'AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE' are not set");
+
+ setenv(
+ "AWS_CONTAINER_CREDENTIALS_FULL_URI", zNewFmt("http://%s:%u/v1/credentials", strZ(host), testPortAuth), true);
+
+ TEST_ERROR(
+ storageRepoGet(0, true), OptionInvalidError,
+ "option 'repo1-s3-key-type' is 'pod-id' but 'AWS_CONTAINER_CREDENTIALS_FULL_URI' and"
+ " 'AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE' are not set");
+
+ setenv("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", TEST_PODID_TOKEN_FILE, true);
+
+ s3 = storageRepoGet(0, true);
+ driver = (StorageS3 *)storageDriver(s3);
+
+ TEST_RESULT_STR_Z(driver->tokenFile, TEST_PODID_TOKEN_FILE, "check token file");
+
+ // Set partSize to a small value for testing
+ driver->partSize = 16;
+
+ hrnServerScriptAccept(service);
+
// -----------------------------------------------------------------------------------------------------------------
TEST_TITLE("error without xml");
- testRequestP(service, s3, HTTP_VERB_GET, "/?delimiter=%2F&list-type=2");
+ // Get service credentials
+ hrnServerScriptAccept(auth);
+
+ testRequestP(auth, NULL, HTTP_VERB_GET, TEST_PODID_GET, .authorization = TEST_PODID_TOKEN);
+ testResponseP(
+ auth,
+ .content = zNewFmt(TEST_PODID_RESPONSE, strZ(testS3DateTime(time(NULL) + (S3_CREDENTIAL_RENEW_SEC - 1)))));
+
+ hrnServerScriptClose(auth);
+
+ testRequestP(service, s3, HTTP_VERB_GET, "/?delimiter=%2F&list-type=2", .accessKey = "gg", .securityToken = "mm");
testResponseP(service, .code = 344);
TEST_ERROR(
@@ -1178,6 +1241,16 @@ testRun(void)
// -----------------------------------------------------------------------------------------------------------------
TEST_TITLE("error with xml");
+ // Get service credentials
+ hrnServerScriptAccept(auth);
+
+ testRequestP(auth, NULL, HTTP_VERB_GET, TEST_PODID_GET, .authorization = TEST_PODID_TOKEN);
+ testResponseP(
+ auth,
+ .content = zNewFmt(TEST_PODID_RESPONSE, strZ(testS3DateTime(time(NULL) + (S3_CREDENTIAL_RENEW_SEC * 2)))));
+
+ hrnServerScriptClose(auth);
+
testRequestP(service, s3, HTTP_VERB_GET, "/?delimiter=%2F&list-type=2");
testResponseP(
service, .code = 344,
@@ -1204,6 +1277,9 @@ testRun(void)
"*** Response Content ***:\n"
"SomeOtherCode");
+ // Auth service no longer needed
+ hrnServerScriptEnd(auth);
+
// -----------------------------------------------------------------------------------------------------------------
TEST_TITLE("list basic level");