Skip to content
Merged
6 changes: 6 additions & 0 deletions conf/operator-config.json
Original file line number Diff line number Diff line change
@@ -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
}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggest using an object ;

OperatorRunTimeConfig (or similar) and using that instead of JsonObject

It will improve readability as we know what exactly we need/have to set in config.

Also in followup MRs; Please make sure we remove the unwanted config from operators

Copy link
Contributor

@clarkxuyang clarkxuyang Jan 7, 2025

Choose a reason for hiding this comment

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

@BehnamMozafari Could you give more context why we need to use an additional config file for this? The current config hierarchy - default, local, integ, prod config already covers config for each env. I am not sure where operator-config.config is fit into this hierarchy.
If we want to serve the run time config (config might be changed at start time, as the config can also be taken from/overwrited by env vars), we should read them from the run time system and send in api.

Copy link
Contributor

Choose a reason for hiding this comment

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

(I will explain, please correct if I am wrong @BehnamMozafari )

this is the config, core servers to operators. We are adding this new operator-config in core that servers runtime config core sets for operators.

Since it is not core config, we added a new file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks Abu, yep that's correct, the operator-config is going to be served to operator and will be mounted to core using a configmap.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

suggest using an object ;

OperatorRunTimeConfig (or similar) and using that instead of JsonObject

I agree, this would help in validating any updates to operator-config. I'm considering implementing this in a future MR. However, a concern is that it would require us to redeploy core whenever we want to add a new operator config value to be consumed at run time.

Copy link
Contributor

Choose a reason for hiding this comment

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

How would we add a new operator config value with the JsonObject implementation?

Copy link
Contributor

Choose a reason for hiding this comment

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

Stronger typing and validation on config updates can reduce risks.

Adding new config_values without an E2E test offers no guarantee it won't break. If it passes E2E tests, it's safe for release, otherwise, prioritizing and addressing those issues would be essential.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would we add a new operator config value with the JsonObject implementation?

With the current implementation, we would need to add the new value to the configmap in the deployment repo, we would also have to make the changes in operator to apply the new config value.

2 changes: 2 additions & 0 deletions src/main/java/com/uid2/core/Const.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
4 changes: 3 additions & 1 deletion src/main/java/com/uid2/core/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
38 changes: 35 additions & 3 deletions src/main/java/com/uid2/core/vertx/CoreVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,14 +82,17 @@ 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,
IAttestationTokenService attestationTokenService,
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");

Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand All @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/uid2/core/vertx/Endpoints.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class TestClientSideKeypairMetadataPath {
private IEnclaveIdentifierProvider enclaveIdentifierProvider;

private AttestationService attestationService;
private FileSystem fileSystem;

@Mock
private OperatorJWTTokenProvider operatorJWTTokenProvider;
Expand All @@ -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()));
}

Expand Down
51 changes: 49 additions & 2 deletions src/test/java/com/uid2/core/vertx/TestCoreVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -116,7 +122,18 @@ 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);
if (Objects.equals(path, com.uid2.core.Const.OPERATOR_CONFIG_PATH)) {
Handler<AsyncResult<Buffer>> handler = invocation.getArgument(1);
handler.handle(Future.succeededFuture(Buffer.buffer(operatorConfig)));
}
return null;
});

CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider, fileSystem);
vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow()));

}
Expand Down Expand Up @@ -874,4 +891,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";
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be better if we check json config on startup to make sure it is valid. That will be easier to debug then having it only checked when operator call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the config will be updated at runtime through a configmap, it might not be sufficient to only check it at startup, but checking it whenever it has changed seems like a good idea. I'll implement this in a future mr. Thanks for the feedback!


fakeAuth(Role.OPERATOR);

this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> {
assertEquals(500, response.statusCode());
testContext.completeNow();
})
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,6 +53,7 @@ public class TestServiceLinkMetadataPath {
private IEnclaveIdentifierProvider enclaveIdentifierProvider;

private AttestationService attestationService;
private FileSystem fileSystem;

@Mock
private OperatorJWTTokenProvider operatorJWTTokenProvider;
Expand All @@ -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()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,6 +53,7 @@ public class TestServiceMetadataPath {
private IEnclaveIdentifierProvider enclaveIdentifierProvider;

private AttestationService attestationService;
private FileSystem fileSystem;

@Mock
private OperatorJWTTokenProvider operatorJWTTokenProvider;
Expand All @@ -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()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,17 +62,20 @@ 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";

@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()));
}

Expand Down
6 changes: 5 additions & 1 deletion src/test/java/com/uid2/core/vertx/TestSitesMetadataPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,6 +57,8 @@ public class TestSitesMetadataPath {

private AttestationService attestationService;

private FileSystem fileSystem;

@Mock
private OperatorJWTTokenProvider operatorJWTTokenProvider;
@Mock
Expand All @@ -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()));
}

Expand Down
Loading