Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,17 @@ target_link_libraries( XrdN2NPrefixObj ${XRootD_UTILS_LIBRARIES} ${XRootD_SERVER
add_library( XrdN2NPrefix MODULE "$<TARGET_OBJECTS:XrdN2NPrefixObj>" )
target_link_libraries( XrdN2NPrefix XrdN2NPrefixObj )

#######################
## libXrdAccHttpCallout ##
#######################
add_library( XrdAccHttpCalloutObj OBJECT src/AccHttpCallout.cc )
set_target_properties( XrdAccHttpCalloutObj PROPERTIES POSITION_INDEPENDENT_CODE ON )
target_include_directories( XrdAccHttpCalloutObj PRIVATE ${XRootD_INCLUDE_DIRS} )
target_link_libraries( XrdAccHttpCalloutObj ${XRootD_UTILS_LIBRARIES} ${XRootD_SERVER_LIBRARIES} nlohmann_json::nlohmann_json CURL::libcurl OpenSSL::Crypto )

add_library( XrdAccHttpCallout MODULE "$<TARGET_OBJECTS:XrdAccHttpCalloutObj>" )
target_link_libraries( XrdAccHttpCallout XrdAccHttpCalloutObj )

# Customize module's suffix and, on Linux, hide unnecessary symbols
if( APPLE )
set_target_properties( XrdPelicanHttpCore PROPERTIES OUTPUT_NAME "XrdPelicanHttpCore-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
Expand All @@ -191,6 +202,7 @@ if( APPLE )
set_target_properties( XrdOssGlobus PROPERTIES OUTPUT_NAME "XrdOssGlobus-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
set_target_properties( XrdOssPosc PROPERTIES OUTPUT_NAME "XrdOssPosc-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
set_target_properties( XrdN2NPrefix PROPERTIES OUTPUT_NAME "XrdN2NPrefix-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
set_target_properties( XrdAccHttpCallout PROPERTIES OUTPUT_NAME "XrdAccHttpCallout-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
else()
set_target_properties( XrdPelicanHttpCore PROPERTIES OUTPUT_NAME "XrdPelicanHttpCore-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" )
set_target_properties( XrdS3 PROPERTIES OUTPUT_NAME "XrdS3-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
Expand All @@ -201,13 +213,14 @@ else()
set_target_properties( XrdOssGlobus PROPERTIES OUTPUT_NAME "XrdOssGlobus-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
set_target_properties( XrdOssPosc PROPERTIES OUTPUT_NAME "XrdOssPosc-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
set_target_properties( XrdN2NPrefix PROPERTIES OUTPUT_NAME "XrdN2NPrefix-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-lib-symbols" )
set_target_properties( XrdAccHttpCallout PROPERTIES OUTPUT_NAME "XrdAccHttpCallout-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-acc-symbols" )
#set_target_properties( XrdOfsRedirect PROPERTIES OUTPUT_NAME "XrdOfsRedirect-${XRootD_PLUGIN_VERSION}" SUFFIX ".so" LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/configs/export-ofs-lib-symbols" )
endif()

include(GNUInstallDirs)

install(
TARGETS XrdPelicanHttpCore XrdS3 XrdHTTPServer XrdOssS3 XrdOssHttp XrdOssFilter XrdOssGlobus XrdOssPosc XrdN2NPrefix
TARGETS XrdPelicanHttpCore XrdS3 XrdHTTPServer XrdOssS3 XrdOssHttp XrdOssFilter XrdOssGlobus XrdOssPosc XrdN2NPrefix XrdAccHttpCallout
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

Expand Down Expand Up @@ -237,6 +250,10 @@ if( ENABLE_TESTS )
target_link_libraries( XrdN2NPrefixTesting XrdN2NPrefixObj )
target_include_directories( XrdN2NPrefixTesting INTERFACE ${XRootD_INCLUDE_DIRS} )

add_library( XrdAccHttpCalloutTesting SHARED "$<TARGET_OBJECTS:XrdAccHttpCalloutObj>" )
target_link_libraries( XrdAccHttpCalloutTesting XrdAccHttpCalloutObj )
target_include_directories( XrdAccHttpCalloutTesting INTERFACE ${XRootD_INCLUDE_DIRS} )

find_program(GoWrk go-wrk HINTS "$ENV{HOME}/go/bin")
if( NOT GoWrk )
# Try installing the go-wrk variable to generate a reasonable stress test
Expand Down
162 changes: 162 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ The plugins in the repository include:
in the core XRootD (with the addition of making in-progress files not-visible in the namespace).
- `XrdN2NPrefix`: A Name2Name (N2N) plugin that performs path prefix substitution,
allowing logical paths to be mapped to different physical paths on disk.
- `XrdAccHttpCallout`: An authorization plugin that makes HTTP callouts to an external service
to determine access permissions.


## Building and Installing
Expand Down Expand Up @@ -354,6 +356,166 @@ forward, while `pfn2lfn` (physical to logical) applies them in reverse.
**Note**: When used with `oss.localroot`, the N2N plugin automatically prepends the localroot
to physical paths returned by `lfn2pfn()`.

### Configure the AccHttpCallout Authorization Plugin

The AccHttpCallout plugin provides HTTP-based authorization callouts to an external service.
This allows for flexible, centralized authorization logic implemented in any language that
can serve HTTP requests.

To load the plugin, use the `acc.authlib` directive:

```
acc.authlib libXrdAccHttpCallout.so
```

(an absolute path may be given if `libXrdAccHttpCallout-5.so` does not reside in a system directory)

There are several configuration directives for the AccHttpCallout module:

```
acchttpcallout.endpoint <url>
acchttpcallout.cache_ttl_positive <seconds>
acchttpcallout.cache_ttl_negative <seconds>
acchttpcallout.passthrough [true|false]
acchttpcallout.trace [all|error|warning|info|debug|none]
```

- `acchttpcallout.endpoint`: **(Required)** The HTTP(S) endpoint URL that will be called for
authorization decisions. The plugin will make HTTP GET requests to this endpoint with query
parameters for the path and operation (verb).

Example:
```
acchttpcallout.endpoint https://auth.example.com/authorize
```

- `acchttpcallout.cache_ttl_positive`: Cache time-to-live in seconds for positive (authorized)
responses. Default is 60 seconds. This reduces load on the authorization service by caching
successful authorization decisions.

Example:
```
acchttpcallout.cache_ttl_positive 120
```

- `acchttpcallout.cache_ttl_negative`: Cache time-to-live in seconds for negative (denied)
responses. Default is 30 seconds. This prevents repeated unauthorized requests from
overwhelming the authorization service.

Example:
```
acchttpcallout.cache_ttl_negative 60
```

- `acchttpcallout.passthrough`: A deployment configuration hint that indicates how this plugin
is intended to be used in the authorization chain. When set to `true`, indicates that another
authorization plugin should be configured as a fallback. When set to `false` (default),
indicates this should be the only authorization plugin.

Note: Due to limitations in the XRootD authorization interface, the plugin always returns
the same value (`XrdAccPriv_None`) when it cannot make a positive authorization decision.
The XRootD framework will automatically try the next configured plugin (if any) or deny
access if this is the last/only plugin in the chain. This setting primarily serves as
documentation of the intended deployment configuration.

Example:
```
acchttpcallout.passthrough false
```

- `acchttpcallout.trace`: Controls logging verbosity. Can be specified multiple times (values
are additive) and multiple values can be given per line.

Example:
```
acchttpcallout.trace info
```

#### HTTP Callout Protocol

When a client attempts to access a resource, the plugin makes an HTTP GET request to the
configured endpoint with the following query parameters:

- `path`: The URL-encoded path being accessed (e.g., `/store/data/file.txt`)
- `verb`: The HTTP/WebDAV verb corresponding to the operation (e.g., `GET`, `PUT`, `DELETE`)

The bearer token from the client is passed in the `Authorization` header:

```
Authorization: Bearer <token>
```

The authorization service should respond with:

- **200 OK**: Access is granted
- **401 Unauthorized** or **403 Forbidden**: Access is denied
- **5xx Server Error**: Error executing authorization (treated as failure)

#### Response Format

The response body may optionally contain JSON data with additional information:

```json
{
"authorizations": [
{
"verb": "GET",
"prefixes": ["/store/data", "/store/mc"]
}
],
"user": "jdoe",
"group": "physicists"
}
```

- `authorizations`: An array of additional path prefixes that are authorized for the token.
These are cached to avoid redundant callouts for related paths.
- `verb`: The HTTP/WebDAV verb (e.g., `GET`, `PUT`)
- `prefixes`: Array of path prefixes authorized for this verb

- `user`: Username to add to the security context (optional)
- `group`: Group name to add to the security context (optional)

#### Operation to Verb Mapping

The plugin maps XRootD operations to HTTP/WebDAV verbs as follows:

| XRootD Operation | HTTP/WebDAV Verb |
|-----------------|------------------|
| Read | GET |
| Readdir | PROPFIND |
| Stat | HEAD |
| Update/Create | PUT |
| Delete | DELETE |
| Mkdir | MKCOL |
| Rename/Insert | MOVE |

#### Complete Example Configuration

```
# Enable the HTTP protocol
xrd.protocol http:1094 libXrdHttp.so

# Load the authorization plugin
acc.authlib libXrdAccHttpCallout.so

# Configure the authorization endpoint
acchttpcallout.endpoint https://pelican-auth.example.com/api/v1/authorize

# Configure caching (2 minutes for positive, 1 minute for negative)
acchttpcallout.cache_ttl_positive 120
acchttpcallout.cache_ttl_negative 60

# Don't pass through to other auth plugins on failure
acchttpcallout.passthrough false

# Enable info-level logging
acchttpcallout.trace info

# Export paths
all.export /store
```

## Startup and Testing

Assuming you named the config file `xrootd-http.cfg`, as a non-rootly user run:
Expand Down
7 changes: 7 additions & 0 deletions configs/export-acc-symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
global:
XrdAccAuthorizeObject*;

local:
*;
};
47 changes: 47 additions & 0 deletions configs/xrootd-acchttpcallout-example.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Example XRootD configuration using AccHttpCallout authorization plugin
#
# This configuration demonstrates how to set up XRootD with HTTP-based
# authorization callouts to an external authorization service.

# Enable the HTTP protocol on port 1094
xrd.protocol http:1094 libXrdHttp.so

# Load the AccHttpCallout authorization plugin
acc.authlib libXrdAccHttpCallout.so

# Configure the authorization endpoint
# This is the URL of your authorization service that will receive HTTP GET requests
# with query parameters: ?path=<url-encoded-path>&verb=<HTTP-verb>
# The bearer token will be passed in the Authorization header
acchttpcallout.endpoint https://auth.example.com/api/v1/authorize

# Cache time-to-live for positive (authorized) responses in seconds
# Default: 60 seconds
# Increase this value to reduce load on your authorization service
acchttpcallout.cache_ttl_positive 120

# Cache time-to-live for negative (denied) responses in seconds
# Default: 30 seconds
# This prevents repeated unauthorized requests from overwhelming your service
acchttpcallout.cache_ttl_negative 60

# Passthrough behavior when authorization fails
# - true: Pass the request to the next configured authorization plugin
# - false (default): Immediately deny access on failure
acchttpcallout.passthrough false

# Logging verbosity
# Options: all, error, warning, info, debug, none
acchttpcallout.trace info

# Export paths that XRootD will serve
# Users can access files under these paths (subject to authorization)
all.export /store
all.export /data

# Optional: Disable async mode if needed
# (Some plugins may not work in async mode)
xrootd.async off

# Optional: Set the port for admin interface
# xrd.port 1094
Loading