You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2026-06-16 00:25:48 +02:00
Support for S3 EKS pod identity.
Fetch credentials automatically using EKS pod identity, which removes the need for static configuration. Credentials are automatically updated before they expire to support long-running commands.
This commit is contained in:
@@ -57,6 +57,18 @@
|
||||
<p>Support for <proper>Azure</proper> managed identities.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<github-issue id="2718"/>
|
||||
<github-pull-request id="2719"/>
|
||||
|
||||
<release-item-contributor-list>
|
||||
<release-item-contributor id="pierre.bouteloup"/>
|
||||
<release-item-reviewer id="david.steele"/>
|
||||
</release-item-contributor-list>
|
||||
|
||||
<p><i><b>Experimental</b></i> support for <proper>S3</proper> EKS pod identity.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<github-issue id="2697"/>
|
||||
<github-pull-request id="2700"/>
|
||||
|
||||
@@ -935,6 +935,11 @@
|
||||
<contributor-id type="github">philrhurst</contributor-id>
|
||||
</contributor>
|
||||
|
||||
<contributor id="pierre.bouteloup">
|
||||
<contributor-name-display>Pierre BOUTELOUP</contributor-name-display>
|
||||
<contributor-id type="github">wolrajhti</contributor-id>
|
||||
</contributor>
|
||||
|
||||
<contributor id="pierre.ducroquet">
|
||||
<contributor-name-display>Pierre Ducroquet</contributor-name-display>
|
||||
<contributor-id type="github">PierreDucroquet</contributor-id>
|
||||
|
||||
@@ -2311,6 +2311,7 @@ option:
|
||||
- shared
|
||||
- auto
|
||||
- web-id
|
||||
- pod-id
|
||||
|
||||
repo-s3-key:
|
||||
section: global
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+26
-3
@@ -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),
|
||||
|
||||
@@ -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:
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><Error><Code>SomeOtherCode</Code></Error>");
|
||||
|
||||
// Auth service no longer needed
|
||||
hrnServerScriptEnd(auth);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("list basic level");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user