Skip to content

Commit cad1871

Browse files
[XrdHttp] route http.oidc bearer auth through sec.protocol oidc.
Delegate HTTPS bearer validation to the security framework via CIA so XrdHttp no longer links libXrdOucOIDC directly. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 19c4c5a commit cad1871

5 files changed

Lines changed: 114 additions & 55 deletions

File tree

src/XrdHttp/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,11 @@ set_target_properties(XrdHttpUtils
2828
VERSION ${XRootD_LIBVERSION}
2929
)
3030

31-
set(XrdOucOIDC XrdOucOIDC-${PLUGIN_VERSION})
32-
3331
target_link_libraries(XrdHttpUtils
3432
PRIVATE
3533
XrdServer
3634
XrdUtils
3735
XrdCrypto
38-
${XrdOucOIDC}
3936
PUBLIC
4037
OpenSSL::SSL
4138
OpenSSL::Crypto

src/XrdHttp/XrdHttpProtocol.cc

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
#include "XrdTls/XrdTlsContext.hh"
4747
#include "XrdOuc/XrdOucUtils.hh"
4848
#include "XrdOuc/XrdOucPrivateUtils.hh"
49-
#include "XrdOuc/XrdOucOIDC.hh"
49+
#include "XrdSec/XrdSecLoadSecurity.hh"
5050
#include "XrdHttpCors/XrdHttpCors.hh"
5151

5252
#include <charconv>
@@ -157,6 +157,7 @@ bool xrdctxVer = false;
157157
using namespace XrdHttpProtoInfo;
158158

159159
int XrdHttpProtocol::oidcHttpMode = 0;
160+
const char *XrdHttpProtocol::oidcConfigFN = nullptr;
160161

161162
/******************************************************************************/
162163
/* P r o t o c o l M a n a g e m e n t S t a c k s */
@@ -809,7 +810,7 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here
809810

810811

811812

812-
// Bearer OIDC authentication over HTTPS (shared with sec.protocol oidc).
813+
// Bearer OIDC authentication over HTTPS (via sec.protocol oidc / CIA).
813814
if (ishttps && ssldone && oidcHttpMode && HandleOidcAuthentication()) {
814815
return -1;
815816
}
@@ -912,6 +913,7 @@ int XrdHttpProtocol::Stats(char *buff, int blen, int do_sync) {
912913
eDest.Say("Config http." x " overrides the xrd." y " directive.")
913914

914915
int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) {
916+
XrdHttpProtocol::oidcConfigFN = ConfigFN;
915917
XrdOucEnv cfgEnv;
916918
XrdOucStream Config(&eDest, getenv("XRDINSTANCE"), &cfgEnv, "=====> ");
917919
std::vector<extHInfo> extHIVec;
@@ -3016,44 +3018,22 @@ int XrdHttpProtocol::xoidc(XrdOucStream &Config) {
30163018
if (!val || !val[0])
30173019
{eDest.Emsg("Config", "http.oidc argument not specified"); return 1;}
30183020

3019-
std::string parms;
3020-
bool needInit = false;
3021-
30223021
if (!strcmp(val, "on") || !strcmp(val, "optional"))
30233022
{oidcHttpMode = 1;
3024-
needInit = !XrdOucOIDC::IsConfigured();
3023+
return 0;
30253024
}
3026-
else if (!strcmp(val, "require"))
3025+
if (!strcmp(val, "require"))
30273026
{oidcHttpMode = 2;
3028-
needInit = !XrdOucOIDC::IsConfigured();
3029-
}
3030-
else if (val[0] == '-')
3031-
{oidcHttpMode = 1;
3032-
parms = val;
3033-
char *w = nullptr;
3034-
while ((w = Config.GetWord()))
3035-
{parms.push_back(' ');
3036-
parms += w;
3037-
}
3038-
needInit = true;
3039-
}
3040-
else
3041-
{eDest.Emsg("Config", "invalid http.oidc parameter -", val); return 1;}
3042-
3043-
if (needInit)
3044-
{std::string emsg;
3045-
const char *initParms = parms.empty() ? nullptr : parms.c_str();
3046-
if (!XrdOucOIDC::Init(eDest.logger(), initParms, emsg))
3047-
{eDest.Emsg("Config", "http.oidc initialization failed:", emsg.c_str());
3048-
return 1;
3049-
}
3027+
return 0;
30503028
}
3051-
else if (!XrdOucOIDC::IsConfigured())
3052-
{eDest.Emsg("Config", "http.oidc requires OIDC configuration via sec.protocol oidc or inline parameters");
3029+
if (val[0] == '-')
3030+
{eDest.Emsg("Config", "http.oidc inline parameters are not supported;",
3031+
"configure OIDC via sec.protparm oidc and sec.protocol oidc");
30533032
return 1;
30543033
}
30553034

3056-
return 0;
3035+
eDest.Emsg("Config", "invalid http.oidc parameter -", val);
3036+
return 1;
30573037
}
30583038

30593039
int XrdHttpProtocol::xauth(XrdOucStream &Config) {

src/XrdHttp/XrdHttpProtocol.hh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ private:
239239
/// 0=disabled, 1=optional bearer OIDC, 2=require bearer OIDC
240240
static int oidcHttpMode;
241241

242+
/// Config file path used to load the security framework for http.oidc.
243+
static const char *oidcConfigFN;
244+
242245
static bool isRequiredXtractor; // If true treat secxtractor errors as fatal
243246
static XrdHttpSecXtractor *secxtractor;
244247

src/XrdHttp/XrdHttpSecurity.cc

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@
1919
// along with XRootD. If not, see <http://www.gnu.org/licenses/>.
2020
//------------------------------------------------------------------------------
2121

22+
#include <cctype>
2223
#include <cstdio>
2324
#include <cstring>
2425
#include <map>
2526
#include <string>
27+
#include <vector>
2628

2729
#include <openssl/evp.h>
2830

2931
#include "XrdHttpProtocol.hh"
3032
#include "XrdHttpTrace.hh"
3133
#include "XrdHttpSecXtractor.hh"
32-
#include "XrdOuc/XrdOucOIDC.hh"
34+
#include "XrdSec/XrdSecLoadSecurity.hh"
35+
#include "XrdSec/XrdSecInterface.hh"
36+
#include "XrdOuc/XrdOucErrInfo.hh"
3337
#include "Xrd/XrdLink.hh"
3438
#include "XrdCrypto/XrdCryptoX509Chain.hh"
3539
#include "XrdCrypto/XrdCryptosslAux.hh"
@@ -73,6 +77,41 @@ std::string bearerTokenKey(const char *tok, int tlen)
7377
return std::string(hex, mdLen * 2);
7478
}
7579

80+
bool hasPrefix(const char *s, const char *end, const char *pfx)
81+
{
82+
while (*pfx && s < end && *s == *pfx) {++s; ++pfx;}
83+
return !*pfx;
84+
}
85+
86+
const char *stripBearerToken(const char *bTok, int &sz)
87+
{
88+
const char *sTok = bTok;
89+
sz = 0;
90+
if (!sTok) return nullptr;
91+
92+
const char *endPtr = sTok + strlen(sTok);
93+
while (sTok < endPtr && isspace(static_cast<unsigned char>(*sTok))) ++sTok;
94+
if (sTok >= endPtr) return nullptr;
95+
96+
if ((endPtr - sTok) >= 9 && hasPrefix(sTok, endPtr, "Bearer%20")) sTok += 9;
97+
else if ((endPtr - sTok) >= 7 && hasPrefix(sTok, endPtr, "Bearer ")) sTok += 7;
98+
99+
while (sTok < endPtr && isspace(static_cast<unsigned char>(*sTok))) ++sTok;
100+
if (sTok >= endPtr) return nullptr;
101+
102+
while (endPtr > sTok && isspace(static_cast<unsigned char>(*(endPtr - 1)))) --endPtr;
103+
sz = static_cast<int>(endPtr - sTok);
104+
return (sz > 0 ? sTok : nullptr);
105+
}
106+
107+
void copyEntityAttrs(XrdSecEntity &dst, const XrdSecEntity &src)
108+
{
109+
for (const auto &key : src.eaAPI->Keys())
110+
{std::string val;
111+
if (src.eaAPI->Get(key, val)) dst.eaAPI->Add(key, val, true);
112+
}
113+
}
114+
76115
bool extractBearerToken(const XrdHttpReq &req, std::string &token)
77116
{
78117
const std::string &cgi = req.hdr2cgistr;
@@ -129,6 +168,19 @@ bool XrdHttpProtocol::InitSecurity() {
129168
secxtractor->Init(sslctx, XrdHttpTrace.What);
130169
}
131170

171+
// Load the security framework when HTTP bearer OIDC is enabled.
172+
//
173+
if (oidcHttpMode && !CIA)
174+
{if (!oidcConfigFN)
175+
{eDest.Say("Error: http.oidc requires a configuration file path");
176+
return false;
177+
}
178+
if (!(CIA = XrdSecLoadSecService(&eDest, oidcConfigFN)))
179+
{eDest.Say("Error loading security framework for http.oidc");
180+
return false;
181+
}
182+
}
183+
132184
// All done
133185
//
134186
return true;
@@ -144,7 +196,7 @@ XrdHttpProtocol::HandleOidcAuthentication()
144196
#undef TRACELINK
145197
#define TRACELINK Link
146198

147-
if (!oidcHttpMode || !XrdOucOIDC::IsConfigured()) return 0;
199+
if (!oidcHttpMode || !CIA) return 0;
148200

149201
// Client-certificate identity is fixed for the TLS connection.
150202
if (SecEntity.name && strncmp(SecEntity.prot, "oidc", 4) != 0) return 0;
@@ -160,7 +212,7 @@ XrdHttpProtocol::HandleOidcAuthentication()
160212
}
161213

162214
int tlen = 0;
163-
const char *tok = XrdOucOIDC::StripToken(bearer.c_str(), tlen);
215+
const char *tok = stripBearerToken(bearer.c_str(), tlen);
164216
if (!tok || tlen <= 0)
165217
{TRACEI(REQ, " OIDC bearer token malformed.");
166218
SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false);
@@ -176,19 +228,44 @@ XrdHttpProtocol::HandleOidcAuthentication()
176228

177229
if (!oidcBearerTokKey.empty() && tokKey == oidcBearerTokKey) return 0;
178230

179-
std::string tokStr(tok, static_cast<size_t>(tlen));
180-
std::string identity, emsg;
181-
std::map<std::string, std::string> entityAttrs;
182-
if (!XrdOucOIDC::ValidateToken(tokStr.c_str(), identity, emsg, nullptr,
183-
&entityAttrs))
184-
{TRACEI(REQ, " OIDC token validation failed: " << emsg);
231+
const int bsz = 5 + tlen + 1;
232+
std::vector<char> credBuf(static_cast<size_t>(bsz));
233+
strcpy(credBuf.data(), "oidc");
234+
memcpy(credBuf.data() + 5, tok, static_cast<size_t>(tlen));
235+
credBuf[static_cast<size_t>(5 + tlen)] = '\0';
236+
237+
XrdSecCredentials cred;
238+
cred.buffer = credBuf.data();
239+
cred.size = bsz;
240+
241+
XrdOucErrInfo eMsg;
242+
XrdSecProtocol *authProt = CIA->getProtocol(Link->Host(), *(Link->AddrInfo()),
243+
&cred, eMsg);
244+
if (!authProt)
245+
{int ec = 0;
246+
const char *et = eMsg.getErrText(ec);
247+
TRACEI(REQ, " OIDC protocol unavailable: " << (et && *et ? et : "unknown"));
248+
SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false);
249+
return 1;
250+
}
251+
252+
XrdSecParameters *parm = nullptr;
253+
const int rc = authProt->Authenticate(&cred, &parm, &eMsg);
254+
if (parm) delete parm;
255+
256+
if (rc != 0 || !CIA->PostProcess(authProt->Entity, eMsg))
257+
{int ec = 0;
258+
const char *et = eMsg.getErrText(ec);
259+
TRACEI(REQ, " OIDC token validation failed: " << (et && *et ? et : "unknown"));
260+
authProt->Delete();
185261
SendSimpleResp(401, nullptr, nullptr, "Authentication failed", 0, false);
186262
return 1;
187263
}
188264

189265
if (!oidcBearerTokKey.empty() || Bridge)
190266
{if (Bridge && !Bridge->Disc())
191267
{TRACEI(REQ, " OIDC token changed but bridge is busy.");
268+
authProt->Delete();
192269
SendSimpleResp(503, nullptr, nullptr, "Authentication busy", 0, false);
193270
return 1;
194271
}
@@ -200,10 +277,11 @@ XrdHttpProtocol::HandleOidcAuthentication()
200277
}
201278

202279
if (SecEntity.name) free(SecEntity.name);
203-
SecEntity.name = strdup(identity.c_str());
204-
strncpy(SecEntity.prot, "oidc", sizeof(SecEntity.prot));
205-
for (const auto &attr : entityAttrs)
206-
SecEntity.eaAPI->Add(attr.first, attr.second, true);
280+
SecEntity.name = authProt->Entity.name ? strdup(authProt->Entity.name) : nullptr;
281+
strncpy(SecEntity.prot, authProt->Entity.prot, sizeof(SecEntity.prot));
282+
copyEntityAttrs(SecEntity, authProt->Entity);
283+
authProt->Delete();
284+
207285
oidcBearerTokKey = tokKey;
208286
TRACEI(REQ, " OIDC authenticated as: " << SecEntity.name);
209287
return 0;

src/XrdSecoidc/README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,12 @@ TLS-only use.
103103

104104
## HTTPS (XrdHttp) authentication
105105

106-
The OIDC JWT validation logic is shared with XrdHttp via `libXrdOucOIDC`. To
107-
authenticate HTTPS clients with the same issuers and `oidc.cfg` as `root://`:
106+
HTTPS bearer authentication is routed through the same `sec.protocol oidc` plugin
107+
as `root://` (via the XRootD security framework). Configure OIDC once with
108+
`sec.protparm oidc` / `sec.protocol oidc`, then enable HTTP bearer handling:
108109

109110
```conf
111+
xrootd.seclib libXrdSec.so
110112
xrootd.tls all
111113
http.tlsclientauth off
112114
http.header2cgi Authorization authz
@@ -120,12 +122,11 @@ sec.protocol oidc
120122
- `on` or `optional` — validate `Authorization: Bearer <token>` when present
121123
- `require` — reject requests without a valid bearer token (unless mTLS already
122124
set the identity)
123-
- inline parameters (e.g. `http.oidc -issuer https://... -audience xrootd`) —
124-
initialize OIDC for HTTP only, using the same options as `sec.protocol oidc`
125125

126-
When `sec.protocol oidc` is already configured, `http.oidc on` reuses that shared
127-
configuration. Bearer tokens may be sent via the `Authorization` header (with
128-
`http.header2cgi`) or as an `authz` CGI parameter.
126+
`sec.protocol oidc` (and any `sec.protparm oidc` lines) must be present in the
127+
configuration; `http.oidc` only enables bearer-token handling over HTTPS.
128+
Bearer tokens may be sent via the `Authorization` header (with `http.header2cgi`)
129+
or as an `authz` CGI parameter.
129130

130131
On HTTP keep-alive connections, a changed `Authorization` bearer token is
131132
detected and triggers re-validation, a new `XrdSecEntity`, and a fresh xrootd

0 commit comments

Comments
 (0)