Skip to content

KNOX-3426 - Add DelegationPolicyService schema, interface, and JDBC implementation - #1361

Open
hsheinblatt wants to merge 15 commits into
apache:masterfrom
hsheinblatt:KNOX-3426
Open

KNOX-3426 - Add DelegationPolicyService schema, interface, and JDBC implementation#1361
hsheinblatt wants to merge 15 commits into
apache:masterfrom
hsheinblatt:KNOX-3426

Conversation

@hsheinblatt

Copy link
Copy Markdown
Contributor

KNOX-3426 - Add DelegationPolicyService schema, interface, and JDBC implementation

What changes were proposed in this pull request?

This pull request adds the persistence layer for RFC 8693 delegation policies in Knox IDF.
Delegation policies control which actors (external token issuers and service accounts) are
authorized to perform cross-subject token exchange on behalf of specific users or groups
and for specific resources and scopes.

A five-table schema is added to store policies: a core DELEGATION_REGISTRY table keyed on
(actor_authority, actor_id) with an allow_headless_exchange flag and an optional per-policy
token TTL; four child tables for per-user, per-group, per-resource, and per-resource-scope
entries. Scopes are defined per resource rather than as a flat list, so a policy can allow
different scopes for different target resources. A resource with no scope entries means all
scopes are allowed for that resource.

The DelegationPolicyService gateway service interface is added to gateway-spi, where it is
visible to the filter layer (gateway-provider-security-jwt) that will enforce policies during
token exchange. The interface includes CRUD operations for policy lifecycle management and an
evaluate() method that accepts a PolicyCheckRequest and returns a PolicyDecision.
PolicyCheckRequest and PolicyDecision are also added to gateway-spi as the inputs and outputs
of evaluate(). DelegationPolicy, the POJO representing a stored policy, is also in gateway-spi
since it is returned by the CRUD methods on the same interface.

JdbcDelegationPolicyService, EmptyDelegationPolicyService, DelegationPolicyServiceFactory,
and the DDL scripts for standard, Derby, and Oracle dialects are added to gateway-server,
following the same structure used for JdbcTrustedOidcIssuerService. The factory activates
the JDBC implementation when a KNOXIDF or KNOXIDF_ADMIN topology is deployed, and falls back
to the empty stub otherwise.

The evaluate() implementation checks policies by field: user, groups, headless exchange
flag, subject user list, resource, and per-resource scope. A resource with no defined scopes is
treated as allowing any scope. canActFor.groups support is not yet implemented; if a policy has
a non-empty groups list and the subject is not in the users list, evaluate() returns a server
error rather than silently skipping the group check.

How was this patch tested?

Schema tests verify that all three dialect DDL scripts create the five tables cleanly on a
Derby in-memory database and that NOT NULL, DEFAULT, UNIQUE, and foreign key constraints are
enforced. Default values (status='active', allow_headless_exchange=false, token_ttl_sec=null)
are verified by inserting a row without those fields. Constraint violations for missing required
fields and duplicate (actor_authority, actor_id) are confirmed.

CRUD lifecycle tests cover register, get, update (full overwrite), and delete. Update is verified
to replace child rows completely: registering with two users and updating with three different
users results in exactly three users, not five. Round-trip tests verify that resourcePolicy
entries, including resources with empty scope sets (all-scopes-allowed), are preserved across
register and get, with boundary cases for zero resources, one resource, and multiple resources,
and for resources with no scopes, one scope, and multiple scopes.

evaluate() tests cover the authorized path (actor registered, subject in users list, resource in
policy, scope in resource scopes), the all-scopes-allowed path (resource present but no scope
rows), and all denial paths: actor not registered, subject not in users list (with empty groups
list), both users and groups lists empty (deny - no open delegation), resource not in policy,
scope not in resource scopes, and headless exchange denied by policy flag. The not-yet-implemented
server error path is verified when a non-empty groups list is present and the user check fails.
If no TTL is specified, the default is used, and if specified, the policy value is used.

Factory tests confirm that the JDBC implementation is returned when KNOXIDF or KNOXIDF_ADMIN is
deployed and that the empty stub is returned otherwise.

Integration Tests

No integration tests are added in this pull request. They will be added once the full delegation
enforcement flow is wired in a later task.

Opt-in test suites (PR labels)

N/A

UI changes

N/A

Harrison added 15 commits August 25, 2026 09:52
…More boolean to

indicate the list is truncated.
…tion policy'

along with all helper methods and classes. Change the names of the sql files to
match.
Database manages own connection, service does not handle transactions
or know about child table structure.
Delete cascades so DB handles it, not database class directly.
ServiceLifecycleException only on init, start, stop.
It removed the wrong parameter, so the test values now do not look
like actorIds, but the value doesn't matter for the tests.
…emove some unusual characters: dash and arrow

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the core persistence + SPI surface for KnoxIDF RFC 8693 delegation policies, including a JDBC-backed service implementation and supporting schema/config wiring so the policy enforcement layer can later query and evaluate delegation authorization.

Changes:

  • Introduces DelegationPolicyService SPI (CRUD + evaluate) and supporting immutable request/decision/policy model types in gateway-spi.
  • Adds a new five-table delegation policy schema (standard/Derby/Oracle DDL) and a JDBC implementation with list limits + evaluation logic in gateway-server, including factory-based activation when KnoxIDF roles are deployed.
  • Wires the new service into gateway service initialization, database type SQL selection, and adds unit tests for schema + CRUD/evaluation + factory selection.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
gateway-spi/src/test/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyTest.java Tests immutability/defensive-copy behavior for the DelegationPolicy POJO.
gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java Adds DELEGATION_POLICY_SERVICE to the service type enum.
gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/PolicyDecision.java Adds the evaluation result model (deny reason + effective TTL).
gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/PolicyCheckRequest.java Adds the evaluation request model (actor/subject/resource/scopes/headless).
gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyService.java Adds the SPI interface for delegation policy CRUD + evaluation.
gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyList.java Adds a list wrapper with hasMore truncation signaling.
gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicy.java Adds the policy POJO returned by CRUD and used by persistence.
gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java Adds delegation policy service configuration keys + getters.
gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java Implements new delegation config getters for tests.
gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/delegation/JdbcDelegationPolicyServiceTest.java Adds CRUD, list-limit, evaluation, and TTL behavior tests for the JDBC service.
gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicySchemaTest.java Adds schema/constraint validation tests for Derby + standard SQL.
gateway-server/src/test/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactoryTest.java Tests factory selection (JDBC vs empty) based on deployed topology roles.
gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java Updates ordered service type test to include delegation policy service.
gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ServiceFactory Registers DelegationPolicyServiceFactory via SPI.
gateway-server/src/main/resources/createKnoxIDFDelegationPolicyTablesOracle.sql Adds Oracle DDL for the five-table delegation policy schema.
gateway-server/src/main/resources/createKnoxIDFDelegationPolicyTablesDerby.sql Adds Derby DDL for the five-table delegation policy schema.
gateway-server/src/main/resources/createKnoxIDFDelegationPolicyTables.sql Adds “standard” DDL (IF NOT EXISTS) for the five-table schema.
gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/JdbcDelegationPolicyService.java Implements the service lifecycle, CRUD wiring to DB helper, and policy evaluation logic.
gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/EmptyDelegationPolicyService.java Adds a safe stub implementation used when KnoxIDF roles aren’t deployed.
gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyServiceMessages.java Adds i18n logging message definitions for the new service.
gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyDatabase.java Adds JDBC helper managing schema creation and multi-table persistence.
gateway-server/src/main/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactory.java Adds factory to select JDBC vs empty implementation based on topology roles.
gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java Wires the new service into DefaultGatewayServices initialization.
gateway-server/src/main/java/org/apache/knox/gateway/database/DatabaseType.java Extends DatabaseType to carry the delegation policy DDL resource per dialect.
gateway-server/src/main/java/org/apache/knox/gateway/database/AbstractDataSourceFactory.java Adds constants for delegation policy DDL resource names.
gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java Implements new delegation service config getters.
gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java Updates subject principal assertions to use PrimaryPrincipal.
Suppressed comments (1)

gateway-server/src/test/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactoryTest.java:158

  • Same as above: this test initializes JdbcDelegationPolicyService via the factory, but the GatewayConfigImpl mock doesn’t provide list limit values, so the service will default to 0 for those limits. Add explicit expectations for the list limit getters to reflect production defaults.
    final GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
    EasyMock.expect(config.getDatabaseType()).andReturn(DatabaseType.DERBY.type()).anyTimes();
    EasyMock.expect(config.getDatabaseName())
        .andReturn("memory:" + getClass().getSimpleName() + "_knoxidf_admin").anyTimes();
    EasyMock.expect(config.getDelegationServiceTokenTtlSec()).andReturn(3600).anyTimes();

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +33 to +41
public PolicyCheckRequest(String actorAuthority, String actorId, String subjectName,
String requestedResource, Set<String> requestedScopes, boolean headlessExchange) {
this.actorAuthority = actorAuthority;
this.actorId = actorId;
this.subjectName = subjectName;
this.requestedResource = requestedResource;
this.requestedScopes = requestedScopes;
this.headlessExchange = headlessExchange;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fair.

Comment on lines +61 to +68
this.allowHeadlessExchange = allowHeadlessExchange;
this.canActForUsers = Collections.unmodifiableSet(new HashSet<>(canActForUsers));
this.canActForGroups = Collections.unmodifiableSet(new HashSet<>(canActForGroups));
Map<String, Set<String>> copy = new HashMap<>();
for (Map.Entry<String, Set<String>> entry : resourcePolicy.entrySet()) {
copy.put(entry.getKey(), Collections.unmodifiableSet(new HashSet<>(entry.getValue())));
}
this.resourcePolicy = Collections.unmodifiableMap(copy);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fair enough.

Comment on lines +126 to +129
this.listMaxTotal = listMaxTotal;
this.listMaxPerAuthority = listMaxPerAuthority;
this.selectAllSql = SELECT_ALL_BASE_SQL + " FETCH FIRST " + (listMaxTotal + 1) + " ROWS ONLY";
this.selectAllFilteredSql = SELECT_ALL_BASE_SQL + " WHERE actor_authority = ? FETCH FIRST " + (listMaxPerAuthority + 1) + " ROWS ONLY";

@lmccay lmccay Aug 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting suggestion. @hsheinblatt - thoughts? Seems like any test scenarios would benefit from that being more deterministic.

Comment on lines +142 to +146
if (!JDBCUtils.tableExists(CORE_TABLE, dataSource)) {
try (InputStream is = getClass().getClassLoader().getResourceAsStream(sqlFileName);
Connection connection = dataSource.getConnection()) {
final String script = IOUtils.toString(is, UTF_8);
final StringBuilder stripped = new StringBuilder();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair suggestion.

Comment on lines +61 to +65
// Run Derby DDL once - no IF NOT EXISTS, so run once at class level
runScript(derbyConn, loadSql(AbstractDataSourceFactory.DERBY_KNOXIDF_DELEGATION_POLICY_TABLES_SQL));
// Run standard DDL on HSQLDB
runScript(hsqlConn, loadSql(AbstractDataSourceFactory.KNOXIDF_DELEGATION_POLICY_TABLES_SQL));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is understandable given the lack of oracle backend to test with. @hsheinblatt what do you think about the suggestion here?

Comment on lines +125 to +130
final GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
EasyMock.expect(config.getDatabaseType()).andReturn(DatabaseType.DERBY.type()).anyTimes();
EasyMock.expect(config.getDatabaseName())
.andReturn("memory:" + getClass().getSimpleName() + "_knoxidf").anyTimes();
EasyMock.expect(config.getDelegationServiceTokenTtlSec()).andReturn(3600).anyTimes();
EasyMock.replay(config);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fair.

@lmccay lmccay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a couple comments and raised a concern about the timing of gateway services starting and knowing whether topologies contain knoxidf yet at that time.

Otherwise, I will leave the schema review to @smolnar82 or file a followup jira to track a review for it.

GatewayConfig gatewayConfig, Map<String, String> options, String implementation)
throws ServiceLifecycleException {

String implementationToUse = implementation;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we need an empty implementation. We should be able to configure whether the delegation service should be added or not. I see the check for isKnoxIdfEnabledInAnyTopology and note the intent there. I'm curious of the timing of that and how it is actually known at the time of gateway services being initiatlized.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I don't like the hard coding of classnames rather than usingthe service loader mechanism but I see that this within the factory implementation where the implementation is being provided. I need to dig into that more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants