Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public abstract class AbstractDataSourceFactory {
public static final String DERBY_KNOXIDF_FED_IDENTITY_TABLE_CREATE_SQL_FILE_NAME = "createKnoxIDFFederatedIdentityTableDerby.sql";
public static final String DERBY_KNOXIDF_FED_IDENTITY_ATTR_TABLE_CREATE_SQL_FILE_NAME = "createKnoxIDFFederatedIdentityAttributesTableDerby.sql";

public static final String KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL = "createKnoxIDFTrustedOidcIssuersTable.sql";
public static final String DERBY_KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL = "createKnoxIDFTrustedOidcIssuersTableDerby.sql";
public static final String ORACLE_KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL = "createKnoxIDFTrustedOidcIssuersTableOracle.sql";

public static final String DATABASE_USER_ALIAS_NAME = "gateway_database_user";
public static final String DATABASE_PASSWORD_ALIAS_NAME = "gateway_database_password";
public static final String DATABASE_TRUSTSTORE_PASSWORD_ALIAS_NAME = "gateway_database_ssl_truststore_password";
Expand Down
49 changes: 49 additions & 0 deletions gateway-server/src/main/resources/conf/gateway-site.xml
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,53 @@ limitations under the License.
</property>
-->

<!-- KnoxIDF Trusted OIDC Issuer Service Configuration
These properties configure the TrustedOidcIssuerService gateway service,
which manages the registry of OIDC issuers trusted for dynamic JWKS
discovery. They are gateway-scoped and apply to all topologies that
deploy the KNOXIDF or KNOXIDF_ADMIN service roles.
-->
<!--
<property>
<name>gateway.trustedoidcissuer.max.issuers</name>
<value>10000</value>
<description>
Maximum number of OIDC issuers that may be registered in the trusted
issuer registry. Increasing this value may delay how quickly updates
take effect.
</description>
</property>

<property>
<name>gateway.trustedoidcissuer.discovery.cache.ttl.secs</name>
<value>600</value>
<description>
TTL in seconds for the resolved JWKS URI per trusted issuer. After
expiry the JWKS URI is re-resolved on the next use. Use the admin
API refresh endpoint to force immediate re-resolution without waiting
for TTL expiry.
</description>
</property>

<property>
<name>gateway.trustedoidcissuer.discovery.connect.timeout.ms</name>
<value>3000</value>
<description>
HTTP connect timeout in milliseconds when fetching an OIDC discovery
document (/.well-known/openid-configuration). A short value fails fast
if the issuer endpoint is unreachable.
</description>
</property>

<property>
<name>gateway.trustedoidcissuer.discovery.read.timeout.ms</name>
<value>10000</value>
<description>
HTTP read timeout in milliseconds when fetching an OIDC discovery
document. Allows for slower issuer endpoints while bounding the
latency of a cache-miss resolution.
</description>
</property>
-->

</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with this
-- work for additional information regarding copyright ownership. The ASF
-- licenses this file to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-- License for the specific language governing permissions and limitations under
-- the License.

CREATE TABLE IF NOT EXISTS TRUSTED_OIDC_ISSUERS (
issuer_url VARCHAR(2048) NOT NULL,
dynamic_jwks BOOLEAN DEFAULT false NOT NULL,
cluster_name VARCHAR(256),
registered_at TIMESTAMP NOT NULL,
registered_by VARCHAR(2048),
PRIMARY KEY (issuer_url)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with this
-- work for additional information regarding copyright ownership. The ASF
-- licenses this file to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-- License for the specific language governing permissions and limitations under
-- the License.

CREATE TABLE TRUSTED_OIDC_ISSUERS (
issuer_url VARCHAR(2048) PRIMARY KEY,
dynamic_jwks BOOLEAN DEFAULT false,
cluster_name VARCHAR(256),
registered_at TIMESTAMP,
registered_by VARCHAR(2048)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with this
-- work for additional information regarding copyright ownership. The ASF
-- licenses this file to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-- License for the specific language governing permissions and limitations under
-- the License.

CREATE TABLE TRUSTED_OIDC_ISSUERS (
issuer_url VARCHAR2(2048) NOT NULL,
dynamic_jwks NUMBER(1) DEFAULT 0 NOT NULL,
cluster_name VARCHAR2(256),
registered_at TIMESTAMP(6) NOT NULL,
registered_by VARCHAR2(2048),
PRIMARY KEY (issuer_url)
)
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public void testAddStartAndStop() throws ServiceLifecycleException {
ServiceType.REMOTE_CONFIGURATION_MONITOR,
ServiceType.GATEWAY_STATUS_SERVICE,
ServiceType.LDAP_SERVICE,
ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE
ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE,
ServiceType.TRUSTED_OIDC_ISSUER_SERVICE
};

assertNotEquals(ServiceType.values(), orderedServiceTypes);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.knox.gateway.services.knoxidf.trustedoidcissuer;

import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.Instant;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class TrustedOidcIssuerTest {

@Test
public void testGetters() {
Instant now = Instant.now();
TrustedOidcIssuer issuer = new TrustedOidcIssuer(
"https://issuer.example.com", true, "cluster-a", now, "admin@example.com");

assertEquals("https://issuer.example.com", issuer.getIssuerUrl());
assertTrue(issuer.isDynamicJwks());
assertEquals("cluster-a", issuer.getClusterName());
assertEquals(now, issuer.getRegisteredAt());
assertEquals("admin@example.com", issuer.getRegisteredBy());
}

@Test
public void testNullableOptionalFields() {
TrustedOidcIssuer issuer = new TrustedOidcIssuer(
"https://issuer.example.com", false, null, Instant.now(), null);

assertNull("clusterName should be nullable", issuer.getClusterName());
assertNull("registeredBy should be nullable", issuer.getRegisteredBy());
assertFalse(issuer.isDynamicJwks());
}

@Test
public void testAllFieldsAreFinal() {
for (Field field : TrustedOidcIssuer.class.getDeclaredFields()) {
assertTrue("Field '" + field.getName() + "' must be final for immutability",
Modifier.isFinal(field.getModifiers()));
}
}

@Test
public void testNoSetterMethods() {
for (Method method : TrustedOidcIssuer.class.getDeclaredMethods()) {
assertFalse("Setter found in immutable POJO: " + method.getName(),
method.getName().startsWith("set"));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.knox.gateway.services.knoxidf.trustedoidcissuer;

import org.apache.commons.io.IOUtils;
import org.apache.knox.gateway.database.AbstractDataSourceFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
* Validates that the TRUSTED_OIDC_ISSUERS DDL scripts parse and execute
* correctly against in-memory databases.
*/
public class TrustedOidcIssuersSchemaTest {

private static final String DERBY_DB = "trustedissuers";
private static final String DERBY_URL = "jdbc:derby:memory:" + DERBY_DB + ";create=true";
private static final String DERBY_SHUTDOWN_URL = "jdbc:derby:memory:" + DERBY_DB + ";shutdown=true";
private static final String HSQL_URL = "jdbc:hsqldb:mem:trustedissuersschema;ifexists=false";
private static final String HSQL_USER = "SA";
private static final String HSQL_PASSWORD = "";

private static Connection derbyConn;
private static Connection hsqlConn;

@BeforeClass
public static void setUp() throws SQLException {
derbyConn = DriverManager.getConnection(DERBY_URL);
hsqlConn = DriverManager.getConnection(HSQL_URL, HSQL_USER, HSQL_PASSWORD);
}

@AfterClass
public static void tearDown() throws Exception {
// HSQLDB: follow JDBCTokenStateServiceTest pattern — new connection for SHUTDOWN
try (Connection conn = DriverManager.getConnection(HSQL_URL, HSQL_USER, HSQL_PASSWORD);
Statement stmt = conn.createStatement()) {
stmt.execute("SHUTDOWN");
}

// Derby: close the shared connection before issuing shutdown
if (derbyConn != null && !derbyConn.isClosed()) {
derbyConn.close();
}
try {
DriverManager.getConnection(DERBY_SHUTDOWN_URL);
} catch (SQLException e) {
// Derby signals a successful single-DB shutdown as error code 45000, state "08006"
if (!(e.getErrorCode() == 45000 && "08006".equals(e.getSQLState()))) {
throw e;
}
}
}

/**
* The Derby-dialect DDL must execute without error in a Derby in-memory
* database and leave the table queryable.
*/
@Test
public void testDerbyDdlCreatesTable() throws Exception {
try (Statement stmt = derbyConn.createStatement()) {
stmt.execute(loadSql(AbstractDataSourceFactory.DERBY_KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL));
try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM TRUSTED_OIDC_ISSUERS")) {
assertTrue(rs.next());
assertEquals(0, rs.getInt(1));
}
}
}

/**
* The standard SQL script uses IF NOT EXISTS. Running the script twice must
* not throw, confirming idempotency.
*/
@Test
public void testStandardSqlIdempotent() throws Exception {
String sql = loadSql(AbstractDataSourceFactory.KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL);
try (Statement stmt = hsqlConn.createStatement()) {
stmt.execute(sql);
// Second execution must succeed due to IF NOT EXISTS
stmt.execute(sql);
try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM TRUSTED_OIDC_ISSUERS")) {
assertTrue(rs.next());
assertEquals(0, rs.getInt(1));
}
}
}

private static String loadSql(String fileName) throws IOException {
try (InputStream is = TrustedOidcIssuersSchemaTest.class.getClassLoader().getResourceAsStream(fileName)) {
assertNotNull("SQL file not found on classpath: " + fileName, is);
return IOUtils.toString(is, StandardCharsets.UTF_8);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public enum ServiceType {
REMOTE_CONFIGURATION_MONITOR("RemoteConfigurationMonitor"),
GATEWAY_STATUS_SERVICE("GatewayStatusService"),
LDAP_SERVICE("LDAPService"),
KNOXIDF_FEDERATED_IDENTITY_SERVICE("KnoxIDFFederatedIdentityService");
KNOXIDF_FEDERATED_IDENTITY_SERVICE("KnoxIDFFederatedIdentityService"),
TRUSTED_OIDC_ISSUER_SERVICE("TrustedOidcIssuerService");

private final String serviceTypeName;
private final String shortName;
Expand Down
Loading
Loading