Skip to content

Commit 2427636

Browse files
committed
[Server] add SSO/OIDC/OAUTH0 token auth plugin XrdSecoidc (prot: oidc)
1 parent 3832e34 commit 2427636

12 files changed

Lines changed: 3201 additions & 2 deletions

File tree

debian/xrootd-plugins.install

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
/usr/lib/*/libXrdSecgsiAUTHZVO-5.so
77
/usr/lib/*/libXrdSecgsiGMAPDN-5.so
88
/usr/lib/*/libXrdSeckrb5-5.so
9+
/usr/lib/*/libXrdSecoidc-5.so
910
/usr/lib/*/libXrdSecpwd-5.so
1011
/usr/lib/*/libXrdSecsss-5.so
1112
/usr/lib/*/libXrdSecunix-5.so

python/docs/source/install.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ In this case, the structure is a bit different than before::
166166
| |-- libXrdSecgsiAUTHZVO-5.so
167167
| |-- libXrdSecgsiGMAPDN-5.so
168168
| |-- libXrdSeckrb5-5.so
169+
| |-- libXrdSecoidc-5.so
169170
| |-- libXrdSecpwd-5.so
170171
| |-- libXrdSecsss-5.so
171172
| |-- libXrdSecunix-5.so

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ add_subdirectory( XrdPosix )
5555
add_subdirectory( XrdSec )
5656
add_subdirectory( XrdSecgsi )
5757
add_subdirectory( XrdSeckrb5 )
58+
add_subdirectory( Xrdsecoidc )
5859
add_subdirectory( XrdSecpwd )
5960
add_subdirectory( XrdSecsss )
6061
add_subdirectory( XrdSecunix )

src/XrdSecoidc/README.md

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
# XrdSecOIDC
2+
3+
`XrdSecOIDC` is an XRootD security protocol plugin (`sec.protocol oidc`) that
4+
authenticates bearer-style OIDC tokens over TLS.
5+
6+
This implementation performs OIDC-style JWT validation directly in the OIDC
7+
plugin (no SciTokens helper dependency).
8+
9+
## Client token pickup order
10+
11+
The client side `getCredentials()` searches for a token in this order:
12+
13+
1. `XRD_SSO_TOKEN` (raw token value)
14+
2. `XRD_SSO_TOKEN_FILE` (path to token file)
15+
3. `BEARER_TOKEN` (raw token value, compatibility fallback)
16+
4. `BEARER_TOKEN_FILE` (path to token file, compatibility fallback)
17+
5. `XDG_RUNTIME_DIR/bt_u<uid>`
18+
6. `/tmp/bt_u<uid>`
19+
20+
If no token is found, authentication fails with `ENOPROTOOPT`.
21+
22+
When a token file is used (`*_TOKEN_FILE`, `XDG_RUNTIME_DIR/bt_u<uid>`,
23+
`/tmp/bt_u<uid>`), it must be:
24+
25+
- a regular file (no device/FIFO/etc.),
26+
- owned by the effective client uid,
27+
- accessible by owner only (no group/other permission bits).
28+
29+
## Server initialization parameters
30+
31+
`XrdSecProtocoloidcInit()` supports:
32+
33+
- `-maxsz <num>`: maximum token size (default 8192, max 524288)
34+
- `-expiry {ignore|optional|required}`:
35+
- `ignore` = do not enforce expiry claim
36+
- `optional` = enforce only if expiry present
37+
- `required` = expiry must be present and valid
38+
- `-issuer <url>`: expected `iss` claim value; if `-oidc-config-url` is not
39+
specified, discovery URL defaults to `<issuer>/.well-known/openid-configuration`
40+
- `-audience <value>`: expected token audience (`aud` string or array member);
41+
may be repeated for the current issuer
42+
- `-oidc-config-url <https-url>`: OpenID discovery URL (used to locate JWKS URI)
43+
- `-jwks-url <https-url>`: explicit JWKS endpoint (overrides discovery lookup)
44+
- `-jwks-refresh <seconds>`: JWKS refresh cache interval (default 300)
45+
- `-jwks-cache-file <path>`: optional on-disk JWKS cache file shared across
46+
issuers (disabled by default)
47+
- `-jwks-cache-ttl <seconds>`: TTL for on-disk cached issuer keys
48+
(`0` = use `-jwks-refresh`; default 0)
49+
- `-clock-skew <seconds>`: allowed clock skew for time-based claims (default 60)
50+
- `-identity-claim <claim>`: add claim names (in order) used for user identity;
51+
may be specified multiple times
52+
- `-forced-identity-claim <claim>`: for the current `-issuer`, force identity
53+
extraction to this single claim (no fallback order for that issuer)
54+
- special case: when set to `email`, the email value is mapped to local
55+
username via `[email-map]` entries in `/etc/xrootd/oidc.cfg`
56+
- `-debug-token`: print decoded JWT header/payload on successful auth (debug only;
57+
contains sensitive information)
58+
- `-show-token-claims`: print selected claims only (`alg`, `kid`, `typ`, `iss`,
59+
`aud`, `sub`, `preferred_username`, `azp`, `iat`, `nbf`, `exp`)
60+
- `-token-cache-max <num>`: max number of cached validated tokens (default 10000;
61+
set `0` to disable caching)
62+
- `-token-cache-noexp-ttl <seconds>`: cache TTL for tokens that do not include
63+
`exp` when `-expiry optional|ignore` (default 60)
64+
65+
If no inline parameters are supplied on `sec.protocol oidc`, the plugin
66+
automatically tries to load `/etc/xrootd/oidc.cfg` (INI-style) and maps keys to
67+
the same options listed above.
68+
69+
`-issuer` starts a new issuer policy block. `-audience`, `-oidc-config-url`, and
70+
`-jwks-url` that follow apply to that issuer until the next `-issuer`.
71+
`-forced-identity-claim` is also issuer-scoped.
72+
73+
## TLS requirement
74+
75+
`oidc` rejects non-TLS connections. Both client and server constructors enforce
76+
TLS-only use.
77+
78+
## Server-side identity mapping
79+
80+
After signature and claim validation, the server sets `XrdSecEntity.name` from
81+
claims in this default order:
82+
83+
1. `preferred_username`
84+
2. `upn`
85+
3. `username`
86+
4. `name`
87+
5. `sub`
88+
89+
Use repeated `-identity-claim` options to override this order.
90+
91+
Per-issuer override: use `-forced-identity-claim <claim>` after a specific
92+
`-issuer` to force a single claim for that issuer.
93+
If `forced-identity-claim = email` is used, authentication fails unless the
94+
token has an `email` claim and that email is present in `[email-map]`.
95+
96+
## Example config snippet
97+
98+
```conf
99+
sec.protocol oidc \
100+
-issuer https://issuer-a.example \
101+
-audience xrootd \
102+
-audience xrootd-admin \
103+
-issuer https://issuer-b.example \
104+
-audience service-b \
105+
-expiry required
106+
```
107+
108+
## Standard CERN SSO configuration
109+
110+
Inline `xrootd.cf` style:
111+
112+
```conf
113+
sec.protocol oidc \
114+
-issuer https://auth.cern.ch/auth/realms/cern \
115+
-audience public-client \
116+
-expiry required \
117+
-show-token-claims
118+
```
119+
120+
INI fallback (`/etc/xrootd/oidc.cfg`) equivalent:
121+
122+
```ini
123+
[global]
124+
expiry = required
125+
show-token-claims = true
126+
# issuer configuration
127+
```
128+
129+
## Standard CERN configuration
130+
Example `oidc.cfg` using CERN issuer and default mapping:
131+
```ini
132+
[issuer "https://auth.cern.ch/auth/realms/cern"]
133+
audience = public-client
134+
```
135+
136+
## Standard Google configuration
137+
138+
Example `oidc.cfg` using Google issuer + email mapping:
139+
140+
```ini
141+
[issuer "https://accounts.google.com"]
142+
audience = 780271439668-1aukl5va8p6rbf5i81sgpg5ppr8s2p63.apps.googleusercontent.com
143+
forced-identity-claim = email
144+
145+
[email-map]
146+
foo.bar@gmail.com = foo
147+
```
148+
149+
Google app setup (Google Cloud Console):
150+
151+
1. Create/select a project.
152+
2. Configure OAuth consent screen.
153+
3. Create OAuth client credentials:
154+
- for `xrdsso ... GOOGLE` default flow (`--flow device`), use a client type
155+
that supports device authorization and provide both `client_id` and
156+
`client_secret` to `xrdsso`.
157+
- for `--flow pkce`, use a Desktop app client id.
158+
4. Copy client id/secret and use them in `xrdsso` options (or env vars).
159+
160+
Quickstart (Google):
161+
162+
```sh
163+
# 0) Minimal server config (example: /etc/xrootd/xrootd.cf)
164+
cat > /etc/xrootd/xrootd.cf <<'EOF'
165+
###########################################################
166+
xrootd.seclib libXrdSec.so
167+
all.role server
168+
sec.protocol oidc
169+
xrootd.tls all
170+
xrd.tlsca certdir /etc/grid-security/certificates
171+
xrd.tls /etc/grid-security/xrd/xrdcert.pem /etc/grid-security/xrd/xrdkey.pem
172+
EOF
173+
174+
# 0b) Start the server (foreground example)
175+
xrootd -c /etc/xrootd/xrootd.cf -R xrootd
176+
177+
# 1) Create a token (Google device flow)
178+
./utils/xrdsso create ./build/google.token GOOGLE \
179+
--client-id "<google-oauth-client-id>" \
180+
--client-secret "<google-oauth-client-secret>"
181+
182+
# 2) Inspect token claims
183+
./utils/xrdsso show ./build/google.token
184+
185+
# 3) Try access with xrdfs
186+
XRD_SSO_TOKEN_FILE="$PWD/build/google.token" \
187+
LD_LIBRARY_PATH=/usr/local/lib64 \
188+
/usr/local/bin/xrdfs localhost:2000 stat /tmp/
189+
```
190+
191+
## INI fallback file (`/etc/xrootd/oidc.cfg`)
192+
193+
When `sec.protocol oidc` has no trailing parameters, this file is required and
194+
loaded at plugin init time. If the file is missing, initialization fails.
195+
196+
```ini
197+
[global]
198+
maxsz = 8192
199+
expiry = required
200+
jwks-refresh = 300
201+
jwks-cache-file = /var/lib/xrootd/oidc-jwks-cache.ini
202+
jwks-cache-ttl = 600
203+
clock-skew = 60
204+
identity-claim = preferred_username,sub
205+
show-token-claims = true
206+
token-cache-max = 10000
207+
token-cache-noexp-ttl = 60
208+
209+
[issuer "https://issuer-a.example"]
210+
audience = xrootd,xrootd-admin
211+
forced-identity-claim = preferred_username
212+
# Optional if you want to override discovery default:
213+
# oidc-config-url = https://issuer-a.example/.well-known/openid-configuration
214+
# jwks-url = https://issuer-a.example/protocol/openid-connect/certs
215+
216+
[issuer "https://issuer-b.example"]
217+
audience = service-b
218+
219+
[email-map]
220+
alice@example.org = alice
221+
bob@example.org = bobby
222+
```
223+
224+
Supported sections are `[global]` and `[issuer "<issuer-url>"]` (or `[issuer]`
225+
with `issuer = <url>` inside the section), plus `[email-map]` for
226+
`forced-identity-claim = email`. Boolean keys accept `true/false`, `yes/no`,
227+
`on/off`, `1/0`.
228+
229+
Security checks for `/etc/xrootd/oidc.cfg`:
230+
231+
- must be a regular file,
232+
- must be owned by the effective uid of the running xrootd process,
233+
- must not be writable by group or others.
234+
235+
When `jwks-cache-file` is configured, cached keys are stored per issuer in that
236+
file and reused across refresh/restart cycles until TTL expiry.
237+
238+
## Notes
239+
240+
- Protocol id on the wire is `oidc`.
241+
- Accepted JWT signature algorithm is currently `RS256`.
242+
- OIDC discovery and JWKS endpoints must use `https://`.
243+
- Server-side token cache is enabled by default and keyed by raw token value.
244+
245+
## Local helper scripts
246+
247+
For quick manual testing, helper scripts are available in `utils/`:
248+
249+
- `utils/xrdsso create [tokenfile] [CERN|CERNOIDC|GOOGLE]`: creates token
250+
(`CERN` uses OAuth2 access token flow, `CERNOIDC` requests OIDC scopes and
251+
requires/stores `id_token`, `GOOGLE` defaults to device flow).
252+
- Shortcut: `utils/xrdsso CERNOIDC [tokenfile]` is equivalent to
253+
`utils/xrdsso create [tokenfile] CERNOIDC`.
254+
- `utils/xrdsso show [tokenfile]`: decodes and prints JWT header/payload.
255+
- If `<tokenfile>` is omitted, default is `${XDG_RUNTIME_DIR}/bt_u<uid>`
256+
(or `/tmp/bt_u<uid>` when `XDG_RUNTIME_DIR` is unset).
257+
- For `GOOGLE`, provide `--client-id` (or set `GOOGLE_OAUTH_CLIENT_ID`).
258+
- For `GOOGLE` default device flow, also provide `--client-secret` (or set
259+
`GOOGLE_OAUTH_CLIENT_SECRET`).
260+
- `GOOGLE` flow can be selected via `--flow device|pkce` (`device` default).
261+
- `--client-secret` is optional for PKCE but may be required for some client
262+
types/endpoints; env fallback: `GOOGLE_OAUTH_CLIENT_SECRET`.
263+
- For `GOOGLE`, `xrdsso` stores `id_token` when present (JWT), otherwise
264+
falls back to `access_token`.
265+
- Device flow prints a prefilled verification URL when possible; if your browser
266+
still asks, use the printed device code.
267+
268+
Example:
269+
270+
```sh
271+
./utils/xrdsso create
272+
./utils/xrdsso CERNOIDC
273+
./utils/xrdsso create GOOGLE \
274+
--client-id "<google-oauth-client-id>" \
275+
--client-secret "<google-oauth-client-secret>"
276+
./utils/xrdsso create GOOGLE --flow pkce \
277+
--client-id "<google-oauth-client-id>"
278+
./utils/xrdsso show
279+
```

0 commit comments

Comments
 (0)