1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-15 01:04:37 +02:00

Allow redactions for HTTP queries.

The Azure storage driver exposes secrets in the query when using SAS authorization. These secrets can show up during logging or when an error occurs.

Allow redaction of queries to prevent secrets from being exposed in logs and errors.
This commit is contained in:
David Steele
2020-07-14 13:09:48 -04:00
parent d3dd32a031
commit 91c7adc834
11 changed files with 155 additions and 51 deletions

View File

@ -27,7 +27,7 @@ cmdRepoCreate(void)
{
storageAzureRequestP(
(StorageAzure *)storageDriver(storageRepoWrite()), HTTP_VERB_PUT_STR,
.query = httpQueryAdd(httpQueryNew(), AZURE_QUERY_RESTYPE_STR, AZURE_QUERY_VALUE_CONTAINER_STR));
.query = httpQueryAdd(httpQueryNewP(), AZURE_QUERY_RESTYPE_STR, AZURE_QUERY_VALUE_CONTAINER_STR));
}
}
MEM_CONTEXT_TEMP_END();

View File

@ -17,6 +17,7 @@ struct HttpQuery
{
MemContext *memContext; // Mem context
KeyValue *kv; // KeyValue store
const StringList *redactList; // List of keys to redact values for
};
OBJECT_DEFINE_MOVE(HTTP_QUERY);
@ -24,9 +25,11 @@ OBJECT_DEFINE_FREE(HTTP_QUERY);
/**********************************************************************************************************************************/
HttpQuery *
httpQueryNew(void)
httpQueryNew(HttpQueryNewParam param)
{
FUNCTION_TEST_VOID();
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING_LIST, param.redactList);
FUNCTION_TEST_END();
HttpQuery *this = NULL;
@ -39,6 +42,7 @@ httpQueryNew(void)
{
.memContext = MEM_CONTEXT_NEW(),
.kv = kvNew(),
.redactList = strLstDup(param.redactList),
};
}
MEM_CONTEXT_NEW_END();
@ -101,10 +105,11 @@ httpQueryNewStr(const String *query)
/**********************************************************************************************************************************/
HttpQuery *
httpQueryDup(const HttpQuery *query)
httpQueryDup(const HttpQuery *query, HttpQueryDupParam param)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(HTTP_QUERY, query);
FUNCTION_TEST_PARAM(STRING_LIST, param.redactList);
FUNCTION_TEST_END();
HttpQuery *this = NULL;
@ -120,6 +125,7 @@ httpQueryDup(const HttpQuery *query)
{
.memContext = MEM_CONTEXT_NEW(),
.kv = kvDup(query->kv),
.redactList = param.redactList != NULL ? strLstDup(param.redactList) : strLstDup(query->redactList),
};
}
MEM_CONTEXT_NEW_END();
@ -231,38 +237,61 @@ httpQueryPut(HttpQuery *this, const String *key, const String *value)
}
/**********************************************************************************************************************************/
String *
httpQueryRender(const HttpQuery *this)
bool
httpQueryRedact(const HttpQuery *this, const String *key)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(HTTP_QUERY, this);
FUNCTION_TEST_PARAM(STRING, key);
FUNCTION_TEST_END();
ASSERT(this != NULL);
ASSERT(key != NULL);
FUNCTION_TEST_RETURN(this->redactList != NULL && strLstExists(this->redactList, key));
}
/**********************************************************************************************************************************/
String *
httpQueryRender(const HttpQuery *this, HttpQueryRenderParam param)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(HTTP_QUERY, this);
FUNCTION_TEST_PARAM(BOOL, param.redact);
FUNCTION_TEST_END();
String *result = NULL;
if (this != NULL)
{
MEM_CONTEXT_TEMP_BEGIN()
{
const StringList *keyList = httpQueryList(this);
if (strLstSize(keyList) > 0)
{
result = strNew("");
MEM_CONTEXT_TEMP_BEGIN()
MEM_CONTEXT_PRIOR_BEGIN()
{
result = strNew("");
}
MEM_CONTEXT_PRIOR_END();
for (unsigned int keyIdx = 0; keyIdx < strLstSize(keyList); keyIdx++)
{
const String *key = strLstGet(keyList, keyIdx);
if (strSize(result) != 0)
strCatZ(result, "&");
strCatFmt(
result, "%s=%s", strPtr(strLstGet(keyList, keyIdx)),
strPtr(httpUriEncode(httpQueryGet(this, strLstGet(keyList, keyIdx)), false)));
result, "%s=%s", strPtr(key),
param.redact && httpQueryRedact(this, key) ?
"<redacted>" : strPtr(httpUriEncode(httpQueryGet(this, key), false)));
}
}
}
MEM_CONTEXT_TEMP_END();
}
}
FUNCTION_TEST_RETURN(result);
}
@ -276,12 +305,17 @@ httpQueryToLog(const HttpQuery *this)
for (unsigned int keyIdx = 0; keyIdx < strLstSize(keyList); keyIdx++)
{
const String *key = strLstGet(keyList, keyIdx);
if (strSize(result) != 1)
strCatZ(result, ", ");
strCatFmt(
result, "%s: '%s'", strPtr(strLstGet(keyList, keyIdx)),
strPtr(httpQueryGet(this, strLstGet(keyList, keyIdx))));
strCatFmt(result, "%s: ", strPtr(key));
if (httpQueryRedact(this, key))
strCatZ(result, "<redacted>");
else
strCatFmt(result, "'%s'", strPtr(httpQueryGet(this, key)));
}
strCatZ(result, "}");

View File

@ -19,12 +19,31 @@ typedef struct HttpQuery HttpQuery;
/***********************************************************************************************************************************
Constructors
***********************************************************************************************************************************/
HttpQuery *httpQueryNew(void);
typedef struct HttpQueryNewParam
{
VAR_PARAM_HEADER;
const StringList *redactList; // List of keys to redact values for
} HttpQueryNewParam;
#define httpQueryNewP(...) \
httpQueryNew((HttpQueryNewParam){VAR_PARAM_INIT, __VA_ARGS__})
HttpQuery *httpQueryNew(HttpQueryNewParam param);
// New from encoded query string
HttpQuery *httpQueryNewStr(const String *query);
HttpQuery *httpQueryDup(const HttpQuery *query);
// Duplicate
typedef struct HttpQueryDupParam
{
VAR_PARAM_HEADER;
const StringList *redactList; // List of keys to redact values for
} HttpQueryDupParam;
#define httpQueryDupP(query, ...) \
httpQueryDup(query, (HttpQueryDupParam){VAR_PARAM_INIT, __VA_ARGS__})
HttpQuery *httpQueryDup(const HttpQuery *query, HttpQueryDupParam param);
/***********************************************************************************************************************************
Functions
@ -47,8 +66,20 @@ HttpQuery *httpQueryMove(HttpQuery *this, MemContext *parentNew);
// Put a query item
HttpQuery *httpQueryPut(HttpQuery *this, const String *header, const String *value);
// Should the query key be redacted when logging?
bool httpQueryRedact(const HttpQuery *this, const String *key);
// Render the query for inclusion in an HTTP request
String *httpQueryRender(const HttpQuery *this);
typedef struct HttpQueryRenderParam
{
VAR_PARAM_HEADER;
bool redact; // Redact user-visible query string
} HttpQueryRenderParam;
#define httpQueryRenderP(this, ...) \
httpQueryRender(this, (HttpQueryRenderParam){VAR_PARAM_INIT, __VA_ARGS__})
String *httpQueryRender(const HttpQuery *this, HttpQueryRenderParam param);
/***********************************************************************************************************************************
Destructor

View File

@ -104,7 +104,7 @@ httpRequestProcess(HttpRequest *this, bool requestOnly, bool contentCache)
String *requestStr =
strNewFmt(
"%s %s%s%s " HTTP_VERSION CRLF_Z, strPtr(this->verb), strPtr(httpUriEncode(this->uri, true)),
this->query == NULL ? "" : "?", this->query == NULL ? "" : strPtr(httpQueryRender(this->query)));
this->query == NULL ? "" : "?", this->query == NULL ? "" : strPtr(httpQueryRenderP(this->query)));
// Add headers
const StringList *headerList = httpHeaderList(this->header);
@ -203,7 +203,7 @@ httpRequestNew(HttpClient *client, const String *verb, const String *uri, HttpRe
.client = client,
.verb = strDup(verb),
.uri = strDup(uri),
.query = httpQueryDup(param.query),
.query = httpQueryDupP(param.query),
.header = param.header == NULL ? httpHeaderNew(NULL) : httpHeaderDup(param.header, NULL),
.content = param.content == NULL ? NULL : bufDup(param.content),
};
@ -256,7 +256,7 @@ httpRequestError(const HttpRequest *this, HttpResponse *response)
strCatFmt(error, "\n%s", strPtr(httpUriEncode(this->uri, true)));
if (this->query != NULL)
strCatFmt(error, "?%s", strPtr(httpQueryRender(this->query)));
strCatFmt(error, "?%s", strPtr(httpQueryRenderP(this->query, .redact = true)));
// Output request headers
const StringList *requestHeaderList = httpHeaderList(this->header);

View File

@ -45,6 +45,7 @@ STRING_EXTERN(AZURE_QUERY_COMP_STR, AZURE_QUERY_
STRING_STATIC(AZURE_QUERY_DELIMITER_STR, "delimiter");
STRING_STATIC(AZURE_QUERY_PREFIX_STR, "prefix");
STRING_EXTERN(AZURE_QUERY_RESTYPE_STR, AZURE_QUERY_RESTYPE);
STRING_STATIC(AZURE_QUERY_SIG_STR, "sig");
STRING_STATIC(AZURE_QUERY_VALUE_LIST_STR, "list");
STRING_EXTERN(AZURE_QUERY_VALUE_CONTAINER_STR, AZURE_QUERY_VALUE_CONTAINER);
@ -70,6 +71,7 @@ struct StorageAzure
MemContext *memContext;
HttpClient *httpClient; // Http client to service requests
StringList *headerRedactList; // List of headers to redact from logging
StringList *queryRedactList; // List of query keys to redact from logging
const String *container; // Container to store data in
const String *account; // Account
@ -237,7 +239,10 @@ storageAzureRequestAsync(StorageAzure *this, const String *verb, StorageAzureReq
}
// Make a copy of the query so it can be modified
HttpQuery *query = this->sasKey != NULL && param.query == NULL ? httpQueryNew() : httpQueryDup(param.query);
HttpQuery *query =
this->sasKey != NULL && param.query == NULL ?
httpQueryNewP(.redactList = this->queryRedactList) :
httpQueryDupP(param.query, .redactList = this->queryRedactList);
// Generate authorization header
storageAzureAuth(this, verb, httpUriEncode(param.uri, true), query, httpDateFromTime(time(NULL)), requestHeader);
@ -363,7 +368,7 @@ storageAzureListInternal(
// free memory at regular intervals
MEM_CONTEXT_TEMP_BEGIN()
{
HttpQuery *query = httpQueryNew();
HttpQuery *query = httpQueryNewP();
// Add continuation token from the prior loop if any
if (marker != NULL)
@ -742,6 +747,10 @@ storageAzureNew(
strLstAdd(driver->headerRedactList, HTTP_HEADER_AUTHORIZATION_STR);
strLstAdd(driver->headerRedactList, HTTP_HEADER_DATE_STR);
// Create list of redacted query keys
driver->queryRedactList = strLstNew();
strLstAdd(driver->queryRedactList, AZURE_QUERY_SIG_STR);
// Generate starting file id
cryptoRandomBytes((unsigned char *)&driver->fileId, sizeof(driver->fileId));

View File

@ -141,7 +141,7 @@ storageWriteAzureBlockAsync(StorageWriteAzure *this)
const String *blockId = strNewFmt("%016" PRIX64 "x%07u", this->fileId, strLstSize(this->blockIdList));
// Upload the block and add to block list
HttpQuery *query = httpQueryNew();
HttpQuery *query = httpQueryNewP();
httpQueryAdd(query, AZURE_QUERY_COMP_STR, AZURE_QUERY_VALUE_BLOCK_STR);
httpQueryAdd(query, AZURE_QUERY_BLOCK_ID_STR, blockId);
@ -240,7 +240,7 @@ storageWriteAzureClose(THIS_VOID)
// Finalize the multi-block upload
storageAzureRequestP(
this->storage, HTTP_VERB_PUT_STR, .uri = this->interface.name,
.query = httpQueryAdd(httpQueryNew(), AZURE_QUERY_COMP_STR, AZURE_QUERY_VALUE_BLOCK_LIST_STR),
.query = httpQueryAdd(httpQueryNewP(), AZURE_QUERY_COMP_STR, AZURE_QUERY_VALUE_BLOCK_LIST_STR),
.content = xmlDocumentBuf(blockXml));
}
// Else upload all the data in a single block

View File

@ -173,7 +173,7 @@ storageS3Auth(
String *signedHeaders = NULL;
String *canonicalRequest = strNewFmt(
"%s\n%s\n%s\n", strPtr(verb), strPtr(uri), query == NULL ? "" : strPtr(httpQueryRender(query)));
"%s\n%s\n%s\n", strPtr(verb), strPtr(uri), query == NULL ? "" : strPtr(httpQueryRenderP(query)));
for (unsigned int headerIdx = 0; headerIdx < strLstSize(headerList); headerIdx++)
{
@ -436,7 +436,7 @@ storageS3ListInternal(
// free memory at regular intervals
MEM_CONTEXT_TEMP_BEGIN()
{
HttpQuery *query = httpQueryNew();
HttpQuery *query = httpQueryNewP();
// Add continuation token from the prior loop if any
if (continuationToken != NULL)
@ -700,7 +700,7 @@ storageS3PathRemoveInternal(StorageS3 *this, XmlDocument *request)
const Buffer *response = httpResponseContent(
storageS3RequestP(
this, HTTP_VERB_POST_STR, FSLASH_STR, .query = httpQueryAdd(httpQueryNew(), S3_QUERY_DELETE_STR, EMPTY_STR),
this, HTTP_VERB_POST_STR, FSLASH_STR, .query = httpQueryAdd(httpQueryNewP(), S3_QUERY_DELETE_STR, EMPTY_STR),
.content = xmlDocumentBuf(request)));
// Nothing is returned when there are no errors

View File

@ -128,7 +128,7 @@ storageWriteS3PartAsync(StorageWriteS3 *this)
httpResponseContent(
storageS3RequestP(
this->storage, HTTP_VERB_POST_STR, this->interface.name,
.query = httpQueryAdd(httpQueryNew(), S3_QUERY_UPLOADS_STR, EMPTY_STR)))));
.query = httpQueryAdd(httpQueryNewP(), S3_QUERY_UPLOADS_STR, EMPTY_STR)))));
// Store the upload id
MEM_CONTEXT_BEGIN(this->memContext)
@ -140,7 +140,7 @@ storageWriteS3PartAsync(StorageWriteS3 *this)
}
// Upload the part async
HttpQuery *query = httpQueryNew();
HttpQuery *query = httpQueryNewP();
httpQueryAdd(query, S3_QUERY_UPLOAD_ID_STR, this->uploadId);
httpQueryAdd(query, S3_QUERY_PART_NUMBER_STR, strNewFmt("%u", strLstSize(this->uploadPartList) + 1));
@ -238,7 +238,7 @@ storageWriteS3Close(THIS_VOID)
// Finalize the multi-part upload
storageS3RequestP(
this->storage, HTTP_VERB_POST_STR, this->interface.name,
.query = httpQueryAdd(httpQueryNew(), S3_QUERY_UPLOAD_ID_STR, this->uploadId),
.query = httpQueryAdd(httpQueryNewP(), S3_QUERY_UPLOAD_ID_STR, this->uploadId),
.content = xmlDocumentBuf(partList));
}
// Else upload all the data in a single put

View File

@ -105,30 +105,44 @@ testRun(void)
MEM_CONTEXT_TEMP_BEGIN()
{
TEST_ASSIGN(query, httpQueryNew(), "new query");
StringList *redactList = strLstNew();
strLstAdd(redactList, STRDEF("key2"));
TEST_ASSIGN(query, httpQueryNewP(.redactList = redactList), "new query");
TEST_RESULT_PTR(httpQueryMove(query, memContextPrior()), query, "move to new context");
TEST_RESULT_PTR(httpQueryMove(NULL, memContextPrior()), NULL, "move null to new context");
}
MEM_CONTEXT_TEMP_END();
TEST_RESULT_STR(httpQueryRender(NULL), NULL, "null query renders null");
TEST_RESULT_STR(httpQueryRender(query), NULL, "empty query renders null");
TEST_RESULT_STR(httpQueryRenderP(NULL), NULL, "null query renders null");
TEST_RESULT_STR(httpQueryRenderP(query), NULL, "empty query renders null");
TEST_RESULT_PTR(httpQueryAdd(query, strNew("key2"), strNew("value2")), query, "add query");
TEST_ERROR(httpQueryAdd(query, strNew("key2"), strNew("value2")), AssertError, "key 'key2' already exists");
TEST_RESULT_PTR(httpQueryPut(query, strNew("key2"), strNew("value2a")), query, "put query");
TEST_RESULT_STR_Z(httpQueryRender(query), "key2=value2a", "render one query item");
TEST_RESULT_STR_Z(httpQueryRenderP(query), "key2=value2a", "render one query item");
TEST_RESULT_PTR(httpQueryAdd(query, strNew("key1"), strNew("value 1?")), query, "add query");
TEST_RESULT_STR_Z(strLstJoin(httpQueryList(query), ", "), "key1, key2", "query list");
TEST_RESULT_STR_Z(httpQueryRender(query), "key1=value%201%3F&key2=value2a", "render two query items");
TEST_RESULT_STR_Z(httpQueryRenderP(query), "key1=value%201%3F&key2=value2a", "render two query items");
TEST_RESULT_STR_Z(
httpQueryRenderP(query, .redact = true), "key1=value%201%3F&key2=<redacted>", "render two query items with redaction");
TEST_RESULT_STR_Z(httpQueryGet(query, strNew("key1")), "value 1?", "get value");
TEST_RESULT_STR_Z(httpQueryGet(query, strNew("key2")), "value2a", "get value");
TEST_RESULT_STR(httpQueryGet(query, strNew(BOGUS_STR)), NULL, "get missing value");
TEST_RESULT_STR_Z(httpQueryToLog(query), "{key1: 'value 1?', key2: 'value2a'}", "log output");
TEST_RESULT_STR_Z(httpQueryToLog(query), "{key1: 'value 1?', key2: <redacted>}", "log output");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("dup query with redaction");
StringList *redactList = strLstNew();
strLstAdd(redactList, STRDEF("key1"));
TEST_ASSIGN(query, httpQueryDupP(query, .redactList = redactList), "dup query");
TEST_RESULT_STR_Z(httpQueryToLog(query), "{key1: <redacted>, key2: 'value2a'}", "log output");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("new query from string");
@ -137,13 +151,13 @@ testRun(void)
HttpQuery *query2 = NULL;
TEST_ASSIGN(query2, httpQueryNewStr(STRDEF("?a=%2Bb&c=d%3D")), "query from string");
TEST_RESULT_STR_Z(httpQueryRender(query2), "a=%2Bb&c=d%3D", "render query");
TEST_RESULT_STR_Z(httpQueryRenderP(query2), "a=%2Bb&c=d%3D", "render query");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("merge queries");
TEST_RESULT_STR_Z(
httpQueryRender(httpQueryMerge(query, query2)), "a=%2Bb&c=d%3D&key1=value%201%3F&key2=value2a", "render merge");
httpQueryRenderP(httpQueryMerge(query, query2)), "a=%2Bb&c=d%3D&key1=value%201%3F&key2=value2a", "render merge");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("free query");
@ -352,7 +366,7 @@ testRun(void)
HttpHeader *headerRequest = httpHeaderNew(NULL);
httpHeaderAdd(headerRequest, strNew("host"), strNew("myhost.com"));
HttpQuery *query = httpQueryNew();
HttpQuery *query = httpQueryNewP();
httpQueryAdd(query, strNew("name"), strNew("/path/A Z.txt"));
httpQueryAdd(query, strNew("type"), strNew("test"));
@ -376,7 +390,7 @@ testRun(void)
TEST_RESULT_STR_Z(httpRequestVerb(request), "GET", "check request verb");
TEST_RESULT_STR_Z(httpRequestUri(request), "/", "check request uri");
TEST_RESULT_STR_Z(
httpQueryRender(httpRequestQuery(request)), "name=%2Fpath%2FA%20Z.txt&type=test", "check request query");
httpQueryRenderP(httpRequestQuery(request)), "name=%2Fpath%2FA%20Z.txt&type=test", "check request query");
TEST_RESULT_PTR_NE(httpRequestHeader(request), NULL, "check request headers");
TEST_RESULT_UINT(httpResponseCode(response), 200, "check response code");
@ -484,7 +498,7 @@ testRun(void)
TEST_ASSIGN(
request,
httpRequestNewP(
client, strNew("GET"), strNew("/"), .query = httpQueryAdd(httpQueryNew(), STRDEF("a"), STRDEF("b")),
client, strNew("GET"), strNew("/"), .query = httpQueryAdd(httpQueryNewP(), STRDEF("a"), STRDEF("b")),
.header = headerRequest),
"request");
TEST_ASSIGN(response, httpRequest(request, false), "response");

View File

@ -44,7 +44,7 @@ testRequest(const char *verb, const char *uri, TestRequestParam param)
// When SAS spit out the query and merge in the SAS key
if (driver->sasKey != NULL)
{
HttpQuery *query = httpQueryNew();
HttpQuery *query = httpQueryNewP();
StringList *uriQuery = strLstNewSplitZ(STR(uri), "?");
if (strLstSize(uriQuery) == 2)
@ -54,7 +54,7 @@ testRequest(const char *verb, const char *uri, TestRequestParam param)
strCat(request, strLstGet(uriQuery, 0));
strCatZ(request, "?");
strCat(request, httpQueryRender(query));
strCat(request, httpQueryRenderP(query));
}
// Else just output URI as is
else
@ -235,7 +235,7 @@ testRun(void)
header = httpHeaderAdd(httpHeaderNew(NULL), HTTP_HEADER_CONTENT_LENGTH_STR, STRDEF("44"));
httpHeaderAdd(header, HTTP_HEADER_CONTENT_MD5_STR, STRDEF("b64f49553d5c441652e95697a2c5949e"));
HttpQuery *query = httpQueryAdd(httpQueryNew(), STRDEF("a"), STRDEF("b"));
HttpQuery *query = httpQueryAdd(httpQueryNewP(), STRDEF("a"), STRDEF("b"));
TEST_RESULT_VOID(storageAzureAuth(storage, HTTP_VERB_GET_STR, STRDEF("/path/file"), query, dateTime, header), "auth");
TEST_RESULT_STR_Z(
@ -256,13 +256,13 @@ testRun(void)
16, NULL, 443, 1000, true, NULL, NULL)),
"new azure storage - sas key");
query = httpQueryAdd(httpQueryNew(), STRDEF("a"), STRDEF("b"));
query = httpQueryAdd(httpQueryNewP(), STRDEF("a"), STRDEF("b"));
header = httpHeaderAdd(httpHeaderNew(NULL), HTTP_HEADER_CONTENT_LENGTH_STR, STRDEF("66"));
TEST_RESULT_VOID(storageAzureAuth(storage, HTTP_VERB_GET_STR, STRDEF("/path/file"), query, dateTime, header), "auth");
TEST_RESULT_STR_Z(
httpHeaderToLog(header), "{content-length: '66', host: 'account.blob.core.windows.net'}", "check headers");
TEST_RESULT_STR_Z(httpQueryRender(query), "a=b&sig=key", "check query");
TEST_RESULT_STR_Z(httpQueryRenderP(query), "a=b&sig=key", "check query");
}
// *****************************************************************************************************************************
@ -742,6 +742,22 @@ testRun(void)
TEST_RESULT_VOID(storageRemoveP(storage, strNew("/path/to/missing.txt")), "remove");
// -----------------------------------------------------------------------------------------------------------------
TEST_TITLE("remove files error to check redacted sig");
testRequestP(HTTP_VERB_GET, "?comp=list&restype=container");
testResponseP(.code = 403);
TEST_ERROR_FMT(
storagePathRemoveP(storage, strNew("/"), .recurse = true), ProtocolError,
"HTTP request failed with 403 (Forbidden):\n"
"*** URI/Query ***:\n"
"/account/container?comp=list&restype=container&sig=<redacted>\n"
"*** Request Headers ***:\n"
"content-length: 0\n"
"host: %s",
strPtr(hrnTlsServerHost()));
// -----------------------------------------------------------------------------------------------------------------
TEST_TITLE("remove files from root");

View File

@ -313,7 +313,7 @@ testRun(void)
HttpHeader *header = httpHeaderNew(NULL);
HttpQuery *query = httpQueryNew();
HttpQuery *query = httpQueryNewP();
httpQueryAdd(query, strNew("list-type"), strNew("2"));
TEST_RESULT_VOID(