KNOX-3426 - Add DelegationPolicyService schema, interface, and JDBC implementation - #1361
KNOX-3426 - Add DelegationPolicyService schema, interface, and JDBC implementation#1361hsheinblatt wants to merge 15 commits into
Conversation
…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
There was a problem hiding this comment.
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
DelegationPolicyServiceSPI (CRUD +evaluate) and supporting immutable request/decision/policy model types ingateway-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.
| 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; | ||
| } |
| 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); |
| 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"; |
There was a problem hiding this comment.
Interesting suggestion. @hsheinblatt - thoughts? Seems like any test scenarios would benefit from that being more deterministic.
| 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(); |
| // 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)); | ||
| } |
There was a problem hiding this comment.
This is understandable given the lack of oracle backend to test with. @hsheinblatt what do you think about the suggestion here?
| 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); |
lmccay
left a comment
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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