diff --git a/conf/operator-config.json b/conf/operator-config.json new file mode 100644 index 00000000..817d714d --- /dev/null +++ b/conf/operator-config.json @@ -0,0 +1,6 @@ +{ + "identity_token_expires_after_seconds": 3600, + "refresh_token_expires_after_seconds": 86400, + "refresh_identity_token_after_seconds": 900, + "sharing_token_expiry_seconds": 2592000 +} \ No newline at end of file diff --git a/src/main/java/com/uid2/core/Const.java b/src/main/java/com/uid2/core/Const.java index 22add792..60f2d73c 100644 --- a/src/main/java/com/uid2/core/Const.java +++ b/src/main/java/com/uid2/core/Const.java @@ -17,4 +17,6 @@ public class Config extends com.uid2.shared.Const.Config { public static final String KmsSecretAccessKeyProp = "kms_aws_secret_access_key"; public static final String KmsEndpointProp = "kms_aws_endpoint"; } + + public static final String OPERATOR_CONFIG_PATH = "conf/operator-config.json"; } \ No newline at end of file diff --git a/src/main/java/com/uid2/core/Main.java b/src/main/java/com/uid2/core/Main.java index d57b1cfb..600448b1 100644 --- a/src/main/java/com/uid2/core/Main.java +++ b/src/main/java/com/uid2/core/Main.java @@ -36,6 +36,7 @@ import io.vertx.core.DeploymentOptions; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; +import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.impl.HttpUtils; import io.vertx.core.json.JsonObject; @@ -161,7 +162,8 @@ public static void main(String[] args) { ); JwtService jwtService = new JwtService(config); - coreVerticle = new CoreVerticle(cloudStorage, operatorKeyProvider, attestationService, attestationTokenService, enclaveIdProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider); + FileSystem fileSystem = vertx.fileSystem(); + coreVerticle = new CoreVerticle(cloudStorage, operatorKeyProvider, attestationService, attestationTokenService, enclaveIdProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider, fileSystem); } catch (Exception e) { System.out.println("failed to initialize core verticle: " + e.getMessage()); System.exit(-1); diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index cc23eee9..eb411fb3 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -23,6 +23,7 @@ import com.uid2.shared.vertx.VertxUtils; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; +import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerResponse; @@ -81,6 +82,8 @@ public class CoreVerticle extends AbstractVerticle { private final OperatorJWTTokenProvider operatorJWTTokenProvider; private final RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider; + private final FileSystem fileSystem; + public CoreVerticle(ICloudStorage cloudStorage, IAuthorizableProvider authProvider, AttestationService attestationService, @@ -88,7 +91,8 @@ public CoreVerticle(ICloudStorage cloudStorage, IEnclaveIdentifierProvider enclaveIdentifierProvider, OperatorJWTTokenProvider operatorJWTTokenProvider, JwtService jwtService, - RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider) throws Exception { + RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider, + FileSystem fileSystem) throws Exception { this.operatorJWTTokenProvider = operatorJWTTokenProvider; this.healthComponent.setHealthStatus(false, "not started"); @@ -100,6 +104,8 @@ public CoreVerticle(ICloudStorage cloudStorage, this.enclaveIdentifierProvider.addListener(this.attestationService); this.cloudEncryptionKeyProvider = cloudEncryptionKeyProvider; + this.fileSystem = fileSystem; + final String jwtAudience = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); final String jwtIssuer = ConfigStore.Global.get(Const.Config.CorePublicUrlProp); Boolean enforceJwt = ConfigStore.Global.getBoolean(Const.Config.EnforceJwtProp); @@ -131,8 +137,9 @@ public CoreVerticle(ICloudStorage cloudStorage, IAttestationTokenService attestationTokenService, IEnclaveIdentifierProvider enclaveIdentifierProvider, OperatorJWTTokenProvider jwtTokenProvider, - JwtService jwtService) throws Exception { - this(cloudStorage, authorizableProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, jwtTokenProvider, jwtService, null); + JwtService jwtService, + FileSystem fileSystem) throws Exception { + this(cloudStorage, authorizableProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, jwtTokenProvider, jwtService, null, fileSystem); } @Override @@ -192,6 +199,7 @@ private Router createRoutesSetup() { router.get(Endpoints.OPERATORS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handleOperatorRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.PARTNERS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handlePartnerRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.OPS_HEALTHCHECK.toString()).handler(this::handleHealthCheck); + router.get(Endpoints.OPERATOR_CONFIG.toString()).handler(auth.handle(this::handleGetConfig, Role.OPERATOR)); if (Optional.ofNullable(ConfigStore.Global.getBoolean("enable_test_endpoints")).orElse(false)) { router.route(Endpoints.ATTEST_GET_TOKEN.toString()).handler(auth.handle(this::handleTestGetAttestationToken, Role.OPERATOR)); @@ -200,6 +208,30 @@ private Router createRoutesSetup() { return router; } + private void handleGetConfig(RoutingContext rc) { + fileSystem.readFile(com.uid2.core.Const.OPERATOR_CONFIG_PATH, ar -> { + if (ar.succeeded()) { + try { + String fileContent = ar.result().toString(); + JsonObject configJson = new JsonObject(fileContent); + rc.response() + .putHeader(HttpHeaders.CONTENT_TYPE, "application/json") + .end(configJson.encodePrettily()); + } catch (Exception e) { + rc.response() + .setStatusCode(500) + .end("Failed to parse configuration: " + e.getMessage()); + throw new RuntimeException(e); + } + } else { + rc.response() + .setStatusCode(500) + .end("Failed to retrieve configuration: " + ar.cause().getMessage()); + } + }); + } + + private void handleHealthCheck(RoutingContext rc) { if (HealthManager.instance.isHealthy()) { rc.response().end("OK"); diff --git a/src/main/java/com/uid2/core/vertx/Endpoints.java b/src/main/java/com/uid2/core/vertx/Endpoints.java index 64f48318..7588f67e 100644 --- a/src/main/java/com/uid2/core/vertx/Endpoints.java +++ b/src/main/java/com/uid2/core/vertx/Endpoints.java @@ -20,7 +20,8 @@ public enum Endpoints { SERVICES_REFRESH("/services/refresh"), SERVICE_LINKS_REFRESH("/service_links/refresh"), OPERATORS_REFRESH("/operators/refresh"), - PARTNERS_REFRESH("/partners/refresh"); + PARTNERS_REFRESH("/partners/refresh"), + OPERATOR_CONFIG("/operator/config"); private final String path; diff --git a/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java index 96796eaf..f2284ded 100644 --- a/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestClientSideKeypairMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -53,6 +54,7 @@ public class TestClientSideKeypairMetadataPath { private IEnclaveIdentifierProvider enclaveIdentifierProvider; private AttestationService attestationService; + private FileSystem fileSystem; @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @@ -70,9 +72,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index 78210138..1e5151e5 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -17,6 +17,8 @@ import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; +import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -40,6 +42,8 @@ import javax.crypto.Cipher; import java.io.ByteArrayInputStream; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.SecureRandom; @@ -70,12 +74,14 @@ public class TestCoreVerticle { private JwtService jwtService; @Mock private RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider; + @Mock + private FileSystem fileSystem; private AttestationService attestationService; + private String operatorConfig; private static final String attestationProtocol = "test-attestation-protocol"; private static final String attestationProtocolPublic = "trusted"; - @BeforeEach void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) throws Throwable { JsonObject config = new JsonObject(); @@ -116,7 +122,20 @@ void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) th } }); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider); + operatorConfig = Files.readString(Paths.get(com.uid2.core.Const.OPERATOR_CONFIG_PATH)).trim(); + + when(fileSystem.readFile(anyString(), any())).thenAnswer(invocation -> { + String path = invocation.getArgument(0); + Handler> handler = invocation.getArgument(1); + if (Objects.equals(path, com.uid2.core.Const.OPERATOR_CONFIG_PATH)) { + handler.handle(Future.succeededFuture(Buffer.buffer(operatorConfig))); + } else { + handler.handle(Future.failedFuture(new RuntimeException("Failed to read file: " + path))); + } + return null; + }); + + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } @@ -874,4 +893,34 @@ void keysRefreshSuccessNoHeaderVersion(Vertx vertx, VertxTestContext testContext } }); } + + @Test + void getConfigSuccess(Vertx vertx, VertxTestContext testContext) { + JsonObject expectedConfig = new JsonObject(operatorConfig); + + fakeAuth(Role.OPERATOR); + + // Make HTTP Get request to operator config endpoint + this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { + assertEquals(200, response.statusCode()); + assertEquals("application/json", response.getHeader(HttpHeaders.CONTENT_TYPE)); + JsonObject actualConfig = new JsonObject(response.bodyAsString()); + assertEquals(expectedConfig, actualConfig); + testContext.completeNow(); + }) + )); + } + + @Test + void getConfigInvalidJson(Vertx vertx, VertxTestContext testContext) { + operatorConfig = "invalid config"; + + fakeAuth(Role.OPERATOR); + + this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { + assertEquals(500, response.statusCode()); + testContext.completeNow(); + }) + )); + } } diff --git a/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java index 9784a51b..224dfcae 100644 --- a/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestServiceLinkMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -52,6 +53,7 @@ public class TestServiceLinkMetadataPath { private IEnclaveIdentifierProvider enclaveIdentifierProvider; private AttestationService attestationService; + private FileSystem fileSystem; @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @@ -69,9 +71,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java index cb2aa75d..0fcf0dfc 100644 --- a/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestServiceMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -52,6 +53,7 @@ public class TestServiceMetadataPath { private IEnclaveIdentifierProvider enclaveIdentifierProvider; private AttestationService attestationService; + private FileSystem fileSystem; @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @@ -69,9 +71,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java index dcd7b448..0a2d2dad 100644 --- a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPath.java @@ -15,6 +15,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -59,6 +60,7 @@ public class TestSiteSpecificMetadataPath { private JwtService jwtService; private AttestationService attestationService; + private FileSystem fileSystem; // we need trusted to skip the attestation procedure or otherwise the core encpoint call made in this file will // fail at the attestation handler @@ -67,10 +69,11 @@ public class TestSiteSpecificMetadataPath { @BeforeEach void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable { attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-secrets.json")))); ConfigStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-configs-provide-private-site-data.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java index 6418a34f..626ae95f 100644 --- a/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java +++ b/src/test/java/com/uid2/core/vertx/TestSiteSpecificMetadataPathDisabled.java @@ -15,6 +15,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -61,6 +62,8 @@ public class TestSiteSpecificMetadataPathDisabled { private AttestationService attestationService; + private FileSystem fileSystem; + // we need trusted to skip the attestation procedure or otherwise the core encpoint call made in this file will // fail at the attestation handler private static final String attestationProtocol = "trusted"; @@ -68,10 +71,11 @@ public class TestSiteSpecificMetadataPathDisabled { @BeforeEach void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable { attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-secrets.json")))); ConfigStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testSiteSpecificMetadata/test-configs-stop-providing-private-site-data.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); } diff --git a/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java b/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java index e25e9841..338640d2 100644 --- a/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java +++ b/src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileSystem; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpResponse; @@ -56,6 +57,8 @@ public class TestSitesMetadataPath { private AttestationService attestationService; + private FileSystem fileSystem; + @Mock private OperatorJWTTokenProvider operatorJWTTokenProvider; @Mock @@ -72,9 +75,10 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext) throws Throwable ConfigStore.Global.load(config); attestationService = new AttestationService(); + fileSystem = vertx.fileSystem(); SecretStore.Global.load(((JsonObject) Json.decodeValue(openFile("/com.uid2.core/testGlobalMetadata/test-secrets.json")))); MockitoAnnotations.initMocks(this); - CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); }