Skip to content

Commit a4a97cd

Browse files
authored
Merge 28420ee into sapling-pr-archive-ktf
2 parents f1e0399 + 28420ee commit a4a97cd

4 files changed

Lines changed: 135 additions & 62 deletions

File tree

CCDB/include/CCDB/CcdbApi.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define PROJECT_CCDBAPI_H
1919

2020
#include <string>
21+
#include <string_view>
2122
#include <memory>
2223
#include <map>
2324
#include <curl/curl.h>
@@ -581,7 +582,7 @@ class CcdbApi //: public DatabaseInterface
581582
void initCurlOptionsForRetrieve(CURL* curlHandle, void* pointer, CurlWriteCallback writeCallback, bool followRedirect = true) const;
582583

583584
/// initialize HTTPS header information for the CURL handle. Needs to be given an existing curl_slist* pointer to work with (may be nullptr), which needs to be free by the caller.
584-
void initCurlHTTPHeaderOptionsForRetrieve(CURL* curlHandle, curl_slist*& option_list, long timestamp, std::map<std::string, std::string>* headers, std::string const& etag, const std::string& createdNotAfter, const std::string& createdNotBefore) const;
585+
void initCurlHTTPHeaderOptionsForRetrieve(CURL* curlHandle, curl_slist*& option_list, long timestamp, std::map<std::string, std::string>* headers, std::string const& etag, const std::string& createdNotAfter, const std::string& createdNotBefore, std::string_view url) const;
585586

586587
bool receiveToFile(FILE* fileHandle, std::string const& path, std::map<std::string, std::string> const& metadata,
587588
long timestamp, std::map<std::string, std::string>* headers = nullptr, std::string const& etag = "",

CCDB/src/CcdbApi.cxx

Lines changed: 121 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
#include <regex>
4747
#include <cstdio>
4848
#include <string>
49+
#include <string_view>
50+
#include <utility>
4951
#include <TAlienUserAgent.h>
5052
#include <unordered_set>
5153
#include "rapidjson/document.h"
@@ -62,24 +64,74 @@ unique_ptr<TJAlienCredentials> CcdbApi::mJAlienCredentials = nullptr;
6264

6365
namespace
6466
{
65-
/// Append the gate token, if ALICEO2_CCDB_AUTH_TOKEN names one, to a header list.
67+
/// Strip surrounding whitespace, CR and LF included.
6668
///
67-
/// Set when CCDB is reached through a broker that authenticates its callers.
68-
/// The CI does this so the credential CCDB wants for writes -- a grid
69-
/// certificate -- stays in the broker and never enters the build container,
70-
/// which runs pull-request code. The broker consumes this header and does not
71-
/// forward it, so CCDB itself never sees it.
72-
///
73-
/// Read once into a static: getenv races setenv, and these paths run from
74-
/// several threads. Returns the list unchanged when no token is configured, so
75-
/// callers can apply it unconditionally.
76-
curl_slist* appendGateToken(curl_slist* list)
77-
{
78-
static const std::string header = []() -> std::string {
79-
const char* token = getenv("ALICEO2_CCDB_AUTH_TOKEN");
80-
return (token && *token) ? std::string("Authorization: Bearer ") + token : std::string();
69+
/// A value that keeps its line's trailing CRLF ends the header block early when
70+
/// it is spliced back into a request, silently dropping every header after it.
71+
std::string_view trimHeaderValue(std::string_view value)
72+
{
73+
constexpr std::string_view whitespace = " \t\r\n";
74+
const auto first = value.find_first_not_of(whitespace);
75+
return first == std::string_view::npos
76+
? std::string_view{}
77+
: value.substr(first, value.find_last_not_of(whitespace) - first + 1);
78+
}
79+
80+
/// Gate tokens per endpoint, "<url>=<token>;<url>=<token>", from
81+
/// ALICEO2_CCDB_AUTH_TOKENS. Set when CCDB sits behind a broker that
82+
/// authenticates its callers: the broker mints tokens per route, so a process
83+
/// facing two CCDBs (writable test instance, production) carries one per
84+
/// endpoint. Longest prefix first; read once into a static, since getenv races
85+
/// setenv and these paths run from several threads.
86+
const std::vector<std::pair<std::string, std::string>>& gateTokenTable()
87+
{
88+
static const auto table = []() {
89+
std::vector<std::pair<std::string, std::string>> entries;
90+
const char* spec = getenv("ALICEO2_CCDB_AUTH_TOKENS");
91+
std::string_view rest = spec ? spec : "";
92+
while (!rest.empty()) {
93+
const auto sep = rest.find(';');
94+
const auto entry = trimHeaderValue(rest.substr(0, sep));
95+
rest = (sep == std::string_view::npos) ? std::string_view{} : rest.substr(sep + 1);
96+
const auto eq = entry.find('=');
97+
if (eq == std::string_view::npos) {
98+
continue;
99+
}
100+
auto url = trimHeaderValue(entry.substr(0, eq));
101+
// Trimmed: a stray newline in a token makes the request malformed, which
102+
// a strict broker rejects with an opaque 400 rather than an auth error.
103+
const auto token = trimHeaderValue(entry.substr(eq + 1));
104+
while (url.size() > 1 && url.back() == '/') { // normalise, so the boundary test below is exact
105+
url.remove_suffix(1);
106+
}
107+
if (!url.empty() && !token.empty()) {
108+
entries.emplace_back(std::string(url), std::string("Authorization: Bearer ").append(token));
109+
}
110+
}
111+
std::sort(entries.begin(), entries.end(),
112+
[](const auto& a, const auto& b) { return a.first.size() > b.first.size(); });
113+
return entries;
81114
}();
82-
return header.empty() ? list : curl_slist_append(list, header.c_str());
115+
return table;
116+
}
117+
118+
/// Append the gate token for `url`, if any, to a header list.
119+
///
120+
/// The URL decides the token, so a multi-host pool (initHostsPool splits on
121+
/// ',') gets the right one per host -- which also means the list must be built
122+
/// per host, never shared across a pool. Matching stops at a path boundary:
123+
/// ".../ccdb" is a prefix of ".../ccdb-prod", and a bare startswith would hand
124+
/// production the test instance's token whenever the production entry is
125+
/// missing -- an opaque 401. No match, no token.
126+
curl_slist* appendGateToken(curl_slist* list, std::string_view url)
127+
{
128+
for (const auto& [prefix, header] : gateTokenTable()) {
129+
if (url.substr(0, prefix.size()) == prefix &&
130+
(url.size() == prefix.size() || url[prefix.size()] == '/')) {
131+
return curl_slist_append(list, header.c_str());
132+
}
133+
}
134+
return list;
83135
}
84136
} // namespace
85137

@@ -447,16 +499,9 @@ int CcdbApi::storeAsBinaryFile(const char* buffer, size_t size, const std::strin
447499
curl_mime_data(field, "", 0);
448500
}
449501

450-
struct curl_slist* headerlist = nullptr;
451-
static const char buf[] = "Expect:";
452-
headerlist = curl_slist_append(headerlist, buf);
453-
454-
headerlist = appendGateToken(headerlist);
455-
456502
curlSetSSLOptions(curl);
457503

458504
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
459-
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
460505
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
461506
curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
462507
curl_easy_setopt(curl, CURLOPT_TIMEOUT, mCurlTimeoutUpload);
@@ -469,6 +514,11 @@ int CcdbApi::storeAsBinaryFile(const char* buffer, size_t size, const std::strin
469514
/* what URL that receives this POST */
470515
curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());
471516

517+
// Per host: the gate token is per endpoint (see appendGateToken).
518+
struct curl_slist* headerlist = curl_slist_append(nullptr, "Expect:");
519+
headerlist = appendGateToken(headerlist, fullUrl);
520+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
521+
472522
/* Perform the request, res will get the return code */
473523
res = CURL_perform(curl);
474524
/* Check for errors */
@@ -480,13 +530,12 @@ int CcdbApi::storeAsBinaryFile(const char* buffer, size_t size, const std::strin
480530
}
481531
returnValue = res;
482532
}
533+
curl_slist_free_all(headerlist);
483534
}
484535

485536
/* always cleanup */
486537
curl_easy_cleanup(curl);
487538

488-
/* free slist */
489-
curl_slist_free_all(headerlist);
490539
/* free mime */
491540
curl_mime_free(mime);
492541
} else {
@@ -726,7 +775,7 @@ size_t header_map_callback(char* buffer, size_t size, size_t nitems, void* userd
726775
} // namespace
727776

728777
void CcdbApi::initCurlHTTPHeaderOptionsForRetrieve(CURL* curlHandle, curl_slist*& option_list, long timestamp, std::map<std::string, std::string>* headers, std::string const& etag,
729-
const std::string& createdNotAfter, const std::string& createdNotBefore) const
778+
const std::string& createdNotAfter, const std::string& createdNotBefore, std::string_view url) const
730779
{
731780
// struct curl_slist* list = nullptr;
732781
if (!etag.empty()) {
@@ -747,11 +796,11 @@ void CcdbApi::initCurlHTTPHeaderOptionsForRetrieve(CURL* curlHandle, curl_slist*
747796
curl_easy_setopt(curlHandle, CURLOPT_HEADERDATA, headers);
748797
}
749798

750-
option_list = appendGateToken(option_list);
799+
option_list = appendGateToken(option_list, url);
751800

752-
if (option_list) {
753-
curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, option_list);
754-
}
801+
// Unconditionally, nullptr included: the handle is reused across hosts, and
802+
// skipping the set would leave a previous host's freed list installed.
803+
curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, option_list);
755804

756805
curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
757806
}
@@ -783,16 +832,17 @@ bool CcdbApi::receiveObject(void* dataHolder, std::string const& path, std::map<
783832

784833
curlSetSSLOptions(curlHandle);
785834
initCurlOptionsForRetrieve(curlHandle, dataHolder, writeCallback, followRedirect);
786-
curl_slist* option_list = nullptr;
787-
initCurlHTTPHeaderOptionsForRetrieve(curlHandle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore);
788-
789835
long responseCode = 0;
790836
CURLcode curlResultCode = CURL_LAST;
791837

792838
for (size_t hostIndex = 0; hostIndex < hostsPool.size() && (responseCode >= 400 || curlResultCode > 0); hostIndex++) {
793839
std::string fullUrl = getFullUrlForRetrieval(curlHandle, path, metadata, timestamp, hostIndex);
794840
curl_easy_setopt(curlHandle, CURLOPT_URL, fullUrl.c_str());
795841

842+
// Per host: the gate token is per endpoint (see appendGateToken).
843+
curl_slist* option_list = nullptr;
844+
initCurlHTTPHeaderOptionsForRetrieve(curlHandle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore, fullUrl);
845+
796846
curlResultCode = CURL_perform(curlHandle);
797847

798848
if (curlResultCode != CURLE_OK) {
@@ -811,9 +861,9 @@ bool CcdbApi::receiveObject(void* dataHolder, std::string const& path, std::map<
811861
}
812862
}
813863
}
864+
curl_slist_free_all(option_list);
814865
}
815866

816-
curl_slist_free_all(option_list);
817867
curl_easy_cleanup(curlHandle);
818868
}
819869
return false;
@@ -1213,11 +1263,15 @@ void* CcdbApi::retrieveFromTFile(std::type_info const& tinfo, std::string const&
12131263
}
12141264

12151265
curl_slist* option_list = nullptr;
1216-
initCurlHTTPHeaderOptionsForRetrieve(curl_handle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore);
1266+
initCurlHTTPHeaderOptionsForRetrieve(curl_handle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore, fullUrl);
12171267
auto content = navigateURLsAndRetrieveContent(curl_handle, fullUrl, tinfo, headers);
12181268

12191269
for (size_t hostIndex = 1; hostIndex < hostsPool.size() && !(content); hostIndex++) {
12201270
fullUrl = getFullUrlForRetrieval(curl_handle, path, metadata, timestamp, hostIndex);
1271+
// Per host: the gate token is per endpoint (see appendGateToken).
1272+
curl_slist_free_all(option_list);
1273+
option_list = nullptr;
1274+
initCurlHTTPHeaderOptionsForRetrieve(curl_handle, option_list, timestamp, headers, etag, createdNotAfter, createdNotBefore, fullUrl);
12211275
content = navigateURLsAndRetrieveContent(curl_handle, fullUrl, tinfo, headers);
12221276
}
12231277
if (content) {
@@ -1255,18 +1309,6 @@ std::string CcdbApi::list(std::string const& path, bool latestOnly, std::string
12551309
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
12561310
curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
12571311

1258-
struct curl_slist* headers = nullptr;
1259-
headers = curl_slist_append(headers, (std::string("Accept: ") + returnFormat).c_str());
1260-
headers = curl_slist_append(headers, (std::string("Content-Type: ") + returnFormat).c_str());
1261-
if (createdNotAfter >= 0) {
1262-
headers = curl_slist_append(headers, ("If-Not-After: " + std::to_string(createdNotAfter)).c_str());
1263-
}
1264-
if (createdNotBefore >= 0) {
1265-
headers = curl_slist_append(headers, ("If-Not-Before: " + std::to_string(createdNotBefore)).c_str());
1266-
}
1267-
headers = appendGateToken(headers);
1268-
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
1269-
12701312
curlSetSSLOptions(curl);
12711313

12721314
std::string fullUrl;
@@ -1277,12 +1319,25 @@ std::string CcdbApi::list(std::string const& path, bool latestOnly, std::string
12771319
fullUrl += path;
12781320
curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());
12791321

1322+
// Per host: the gate token is per endpoint (see appendGateToken).
1323+
struct curl_slist* headers = nullptr;
1324+
headers = curl_slist_append(headers, (std::string("Accept: ") + returnFormat).c_str());
1325+
headers = curl_slist_append(headers, (std::string("Content-Type: ") + returnFormat).c_str());
1326+
if (createdNotAfter >= 0) {
1327+
headers = curl_slist_append(headers, ("If-Not-After: " + std::to_string(createdNotAfter)).c_str());
1328+
}
1329+
if (createdNotBefore >= 0) {
1330+
headers = curl_slist_append(headers, ("If-Not-Before: " + std::to_string(createdNotBefore)).c_str());
1331+
}
1332+
headers = appendGateToken(headers, fullUrl);
1333+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
1334+
12801335
res = CURL_perform(curl);
12811336
if (res != CURLE_OK) {
12821337
LOGP(alarm, "CURL_perform() failed: {}", curl_easy_strerror(res));
12831338
}
1339+
curl_slist_free_all(headers);
12841340
}
1285-
curl_slist_free_all(headers);
12861341
curl_easy_cleanup(curl);
12871342
}
12881343

@@ -1319,16 +1374,21 @@ void CcdbApi::deleteObject(std::string const& path, long timestamp) const
13191374
fullUrl << getHostUrl(hostIndex) << "/" << path << "/" << timestampLocal;
13201375
curl_easy_setopt(curl, CURLOPT_URL, fullUrl.str().c_str());
13211376

1377+
// A DELETE is a write, so it needs the gate token as storing does -- per
1378+
// host, since the token is per endpoint (see appendGateToken).
1379+
struct curl_slist* list = appendGateToken(nullptr, fullUrl.str());
1380+
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
1381+
13221382
// Perform the request, res will get the return code
13231383
res = CURL_perform(curl);
13241384
if (res != CURLE_OK) {
13251385
LOGP(alarm, "CURL_perform() failed: {}", curl_easy_strerror(res));
13261386
}
1387+
curl_slist_free_all(list);
13271388
}
13281389
// After the loop, not inside it: cleaning up per host left every later
13291390
// iteration using a freed handle.
13301391
curl_easy_cleanup(curl);
1331-
curl_slist_free_all(list);
13321392
}
13331393
}
13341394

@@ -1353,7 +1413,7 @@ void CcdbApi::truncate(std::string const& path) const
13531413
// does. This was the one write path left without it, which a broker
13541414
// answers 401 -- failing every CCDB suite in their teardown, since each
13551415
// one truncates the path it just wrote.
1356-
struct curl_slist* list = appendGateToken(nullptr);
1416+
struct curl_slist* list = appendGateToken(nullptr, fullUrl.str());
13571417
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
13581418
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
13591419
curlSetSSLOptions(curl);
@@ -1512,7 +1572,7 @@ std::map<std::string, std::string> CcdbApi::retrieveHeaders(std::string const& p
15121572
if (curl != nullptr) {
15131573
struct curl_slist* list = nullptr;
15141574
list = curl_slist_append(list, ("If-None-Match: " + std::to_string(timestamp)).c_str());
1515-
list = appendGateToken(list);
1575+
list = appendGateToken(list, fullUrl);
15161576

15171577
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
15181578

@@ -1591,7 +1651,7 @@ bool CcdbApi::getCCDBEntryHeaders(std::string const& url, std::string const& eta
15911651

15921652
struct curl_slist* list = nullptr;
15931653
list = curl_slist_append(list, ("If-None-Match: " + etag).c_str());
1594-
list = appendGateToken(list);
1654+
list = appendGateToken(list, url);
15951655

15961656
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
15971657

@@ -1621,11 +1681,13 @@ void CcdbApi::parseCCDBHeaders(std::vector<std::string> const& headers, std::vec
16211681
{
16221682
static std::string etagHeader = "ETag: ";
16231683
static std::string locationHeader = "Content-Location: ";
1684+
// Trimmed: `headers` holds raw header lines, CRLF and all, and the etag goes
1685+
// straight back out as an If-None-Match request header.
16241686
for (auto h : headers) {
16251687
if (h.find(etagHeader) == 0) {
1626-
etag = std::string(h.data() + etagHeader.size());
1688+
etag = trimHeaderValue(std::string_view(h).substr(etagHeader.size()));
16271689
} else if (h.find(locationHeader) == 0) {
1628-
pfns.emplace_back(std::string(h.data() + locationHeader.size(), h.size() - locationHeader.size()));
1690+
pfns.emplace_back(trimHeaderValue(std::string_view(h).substr(locationHeader.size())));
16291691
}
16301692
}
16311693
}
@@ -1692,8 +1754,6 @@ int CcdbApi::updateMetadata(std::string const& path, std::map<std::string, std::
16921754
curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
16931755
if (curl != nullptr) {
16941756
CURLcode res;
1695-
// A PUT is a write, so it needs the gate token as storing does.
1696-
struct curl_slist* list = appendGateToken(nullptr);
16971757
for (size_t hostIndex = 0; hostIndex < hostsPool.size(); hostIndex++) {
16981758
// Inside the loop: hoisted out, the stream accumulates and the second
16991759
// host's URL is the first with the second appended.
@@ -1724,6 +1784,9 @@ int CcdbApi::updateMetadata(std::string const& path, std::map<std::string, std::
17241784
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); // make sure we use PUT
17251785
curl_easy_setopt(curl, CURLOPT_USERAGENT, mUniqueAgentID.c_str());
17261786
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
1787+
// A PUT is a write, so it needs the gate token as storing does -- per
1788+
// host, since the token is per endpoint (see appendGateToken).
1789+
struct curl_slist* list = appendGateToken(nullptr, fullUrl.str());
17271790
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
17281791
curlSetSSLOptions(curl);
17291792

@@ -1735,12 +1798,12 @@ int CcdbApi::updateMetadata(std::string const& path, std::map<std::string, std::
17351798
} else {
17361799
ret = 0;
17371800
}
1801+
curl_slist_free_all(list);
17381802
}
17391803
}
17401804
// After the loop, not inside it: cleaning up per host left every later
17411805
// iteration using a freed handle.
17421806
curl_easy_cleanup(curl);
1743-
curl_slist_free_all(list);
17441807
}
17451808
return ret;
17461809
}
@@ -1802,7 +1865,7 @@ void CcdbApi::scheduleDownload(RequestContext& requestContext, size_t* requestCo
18021865
std::string fullUrl = getFullUrlForRetrieval(curl_handle, requestContext.path, requestContext.metadata, requestContext.timestamp);
18031866
curl_slist* options_list = nullptr;
18041867
initCurlHTTPHeaderOptionsForRetrieve(curl_handle, options_list, requestContext.timestamp, &requestContext.headers,
1805-
requestContext.etag, requestContext.createdNotAfter, requestContext.createdNotBefore);
1868+
requestContext.etag, requestContext.createdNotAfter, requestContext.createdNotBefore, fullUrl);
18061869

18071870
data->headers = &requestContext.headers;
18081871
data->hosts = hostsPool;

0 commit comments

Comments
 (0)